mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-22 09:49:30 +01:00
Implement start of backups payment integration work.
This commit is contained in:
committed by
Greyson Parrelli
parent
680223c4b6
commit
6b50be78c0
@@ -7,13 +7,13 @@ import androidx.navigation.NavDirections
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import io.reactivex.rxjava3.subjects.PublishSubject
|
||||
import io.reactivex.rxjava3.subjects.Subject
|
||||
import org.signal.donations.InAppPaymentType
|
||||
import org.thoughtcrime.securesms.MainActivity
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsActivity
|
||||
import org.thoughtcrime.securesms.components.settings.app.notifications.profiles.EditNotificationProfileScheduleFragmentArgs
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.DonationPaymentComponent
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.InAppPaymentComponent
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.StripeRepository
|
||||
import org.thoughtcrime.securesms.database.InAppPaymentTable
|
||||
import org.thoughtcrime.securesms.help.HelpFragment
|
||||
import org.thoughtcrime.securesms.keyvalue.SettingsValues
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
@@ -31,12 +31,12 @@ private const val NOTIFICATION_CATEGORY = "android.intent.category.NOTIFICATION_
|
||||
private const val STATE_WAS_CONFIGURATION_UPDATED = "app.settings.state.configuration.updated"
|
||||
private const val EXTRA_PERFORM_ACTION_ON_CREATE = "extra_perform_action_on_create"
|
||||
|
||||
class AppSettingsActivity : DSLSettingsActivity(), DonationPaymentComponent {
|
||||
class AppSettingsActivity : DSLSettingsActivity(), InAppPaymentComponent {
|
||||
|
||||
private var wasConfigurationUpdated = false
|
||||
|
||||
override val stripeRepository: StripeRepository by lazy { StripeRepository(this) }
|
||||
override val googlePayResultPublisher: Subject<DonationPaymentComponent.GooglePayResult> = PublishSubject.create()
|
||||
override val googlePayResultPublisher: Subject<InAppPaymentComponent.GooglePayResult> = PublishSubject.create()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?, ready: Boolean) {
|
||||
if (intent?.hasExtra(ARG_NAV_GRAPH) != true) {
|
||||
@@ -57,8 +57,8 @@ class AppSettingsActivity : DSLSettingsActivity(), DonationPaymentComponent {
|
||||
StartLocation.PROXY -> AppSettingsFragmentDirections.actionDirectToEditProxyFragment()
|
||||
StartLocation.NOTIFICATIONS -> AppSettingsFragmentDirections.actionDirectToNotificationsSettingsFragment()
|
||||
StartLocation.CHANGE_NUMBER -> AppSettingsFragmentDirections.actionDirectToChangeNumberFragment()
|
||||
StartLocation.SUBSCRIPTIONS -> AppSettingsFragmentDirections.actionDirectToDonateToSignal(InAppPaymentTable.Type.RECURRING_DONATION)
|
||||
StartLocation.BOOST -> AppSettingsFragmentDirections.actionDirectToDonateToSignal(InAppPaymentTable.Type.ONE_TIME_DONATION)
|
||||
StartLocation.SUBSCRIPTIONS -> AppSettingsFragmentDirections.actionDirectToCheckout(InAppPaymentType.RECURRING_DONATION)
|
||||
StartLocation.BOOST -> AppSettingsFragmentDirections.actionDirectToCheckout(InAppPaymentType.ONE_TIME_DONATION)
|
||||
StartLocation.MANAGE_SUBSCRIPTIONS -> AppSettingsFragmentDirections.actionDirectToManageDonations()
|
||||
StartLocation.NOTIFICATION_PROFILES -> AppSettingsFragmentDirections.actionDirectToNotificationProfiles()
|
||||
StartLocation.CREATE_NOTIFICATION_PROFILE -> AppSettingsFragmentDirections.actionDirectToCreateNotificationProfiles()
|
||||
@@ -128,7 +128,7 @@ class AppSettingsActivity : DSLSettingsActivity(), DonationPaymentComponent {
|
||||
@Suppress("DEPRECATION")
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
googlePayResultPublisher.onNext(DonationPaymentComponent.GooglePayResult(requestCode, resultCode, data))
|
||||
googlePayResultPublisher.onNext(InAppPaymentComponent.GooglePayResult(requestCode, resultCode, data))
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
package org.thoughtcrime.securesms.components.settings.app.chats
|
||||
|
||||
import android.content.Intent
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.navigation.Navigation
|
||||
import org.signal.donations.InAppPaymentType
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.backup.v2.ui.subscription.MessageBackupsFlowActivity
|
||||
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
|
||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment
|
||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsText
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.donate.CheckoutFlowActivity
|
||||
import org.thoughtcrime.securesms.components.settings.configure
|
||||
import org.thoughtcrime.securesms.util.RemoteConfig
|
||||
import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter
|
||||
@@ -92,7 +92,7 @@ class ChatsSettingsFragment : DSLSettingsFragment(R.string.preferences_chats__ch
|
||||
if (state.remoteBackupsEnabled) {
|
||||
Navigation.findNavController(requireView()).safeNavigate(R.id.action_chatsSettingsFragment_to_remoteBackupsSettingsFragment)
|
||||
} else {
|
||||
startActivity(Intent(requireContext(), MessageBackupsFlowActivity::class.java))
|
||||
startActivity(CheckoutFlowActivity.createIntent(requireContext(), InAppPaymentType.RECURRING_BACKUP))
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
|
||||
package org.thoughtcrime.securesms.components.settings.app.chats.backups
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.compose.foundation.background
|
||||
@@ -38,6 +37,8 @@ import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.greenrobot.eventbus.Subscribe
|
||||
import org.greenrobot.eventbus.ThreadMode
|
||||
@@ -50,12 +51,14 @@ import org.signal.core.ui.Scaffolds
|
||||
import org.signal.core.ui.SignalPreview
|
||||
import org.signal.core.ui.Snackbars
|
||||
import org.signal.core.ui.Texts
|
||||
import org.signal.core.util.money.FiatMoney
|
||||
import org.signal.donations.InAppPaymentType
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.backup.v2.BackupFrequency
|
||||
import org.thoughtcrime.securesms.backup.v2.BackupV2Event
|
||||
import org.thoughtcrime.securesms.backup.v2.MessageBackupTier
|
||||
import org.thoughtcrime.securesms.backup.v2.ui.subscription.MessageBackupsFlowActivity
|
||||
import org.thoughtcrime.securesms.backup.v2.ui.subscription.getTierDetails
|
||||
import org.thoughtcrime.securesms.backup.v2.ui.subscription.MessageBackupsType
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.donate.CheckoutFlowActivity
|
||||
import org.thoughtcrime.securesms.compose.ComposeFragment
|
||||
import org.thoughtcrime.securesms.conversation.v2.registerForLifecycle
|
||||
import org.thoughtcrime.securesms.payments.FiatMoneyUtil
|
||||
@@ -63,6 +66,8 @@ import org.thoughtcrime.securesms.util.DateUtils
|
||||
import org.thoughtcrime.securesms.util.Util
|
||||
import org.thoughtcrime.securesms.util.navigation.safeNavigate
|
||||
import org.thoughtcrime.securesms.util.viewModel
|
||||
import java.math.BigDecimal
|
||||
import java.util.Currency
|
||||
import java.util.Locale
|
||||
|
||||
/**
|
||||
@@ -82,7 +87,7 @@ class RemoteBackupsSettingsFragment : ComposeFragment() {
|
||||
val callbacks = remember { Callbacks() }
|
||||
|
||||
RemoteBackupsSettingsContent(
|
||||
messageBackupTier = state.messageBackupsTier,
|
||||
messageBackupsType = state.messageBackupsType,
|
||||
lastBackupTimestamp = state.lastBackupTimestamp,
|
||||
canBackUpUsingCellular = state.canBackUpUsingCellular,
|
||||
backupsFrequency = state.backupsFrequency,
|
||||
@@ -101,7 +106,7 @@ class RemoteBackupsSettingsFragment : ComposeFragment() {
|
||||
}
|
||||
|
||||
override fun onEnableBackupsClick() {
|
||||
startActivity(Intent(requireContext(), MessageBackupsFlowActivity::class.java))
|
||||
startActivity(CheckoutFlowActivity.createIntent(requireContext(), InAppPaymentType.RECURRING_BACKUP))
|
||||
}
|
||||
|
||||
override fun onBackUpUsingCellularClick(canUseCellular: Boolean) {
|
||||
@@ -137,7 +142,7 @@ class RemoteBackupsSettingsFragment : ComposeFragment() {
|
||||
}
|
||||
|
||||
override fun onTurnOffAndDeleteBackupsConfirm() {
|
||||
viewModel.turnOffAndDeleteBackups()
|
||||
// TODO [alex] CheckoutFlowStartFragment.launchForBackupsCancellation(childFragmentManager)
|
||||
}
|
||||
|
||||
override fun onBackupsTypeClick() {
|
||||
@@ -159,6 +164,12 @@ class RemoteBackupsSettingsFragment : ComposeFragment() {
|
||||
super.onResume()
|
||||
viewModel.refresh()
|
||||
}
|
||||
|
||||
// override fun onCheckoutFlowResult(result: CheckoutFlowStartFragment.Result) {
|
||||
// if (result is CheckoutFlowStartFragment.Result.CancelationSuccess) {
|
||||
// Snackbar.make(requireView(), R.string.SubscribeFragment__your_subscription_has_been_cancelled, Snackbar.LENGTH_LONG).show()
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -181,7 +192,7 @@ private interface ContentCallbacks {
|
||||
|
||||
@Composable
|
||||
private fun RemoteBackupsSettingsContent(
|
||||
messageBackupTier: MessageBackupTier?,
|
||||
messageBackupsType: MessageBackupsType?,
|
||||
lastBackupTimestamp: Long,
|
||||
canBackUpUsingCellular: Boolean,
|
||||
backupsFrequency: BackupFrequency,
|
||||
@@ -209,13 +220,13 @@ private fun RemoteBackupsSettingsContent(
|
||||
) {
|
||||
item {
|
||||
BackupTypeRow(
|
||||
messageBackupTier = messageBackupTier,
|
||||
messageBackupsType = messageBackupsType,
|
||||
onEnableBackupsClick = contentCallbacks::onEnableBackupsClick,
|
||||
onChangeBackupsTypeClick = contentCallbacks::onBackupsTypeClick
|
||||
)
|
||||
}
|
||||
|
||||
if (messageBackupTier == null) {
|
||||
if (messageBackupsType == null) {
|
||||
item {
|
||||
Rows.TextRow(
|
||||
text = "Payment history",
|
||||
@@ -253,7 +264,7 @@ private fun RemoteBackupsSettingsContent(
|
||||
color = MaterialTheme.colorScheme.onSurface
|
||||
)
|
||||
Text(
|
||||
text = Util.getPrettyFileSize(backupSize ?: 0),
|
||||
text = Util.getPrettyFileSize(backupSize),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
@@ -358,16 +369,14 @@ private fun RemoteBackupsSettingsContent(
|
||||
|
||||
@Composable
|
||||
private fun BackupTypeRow(
|
||||
messageBackupTier: MessageBackupTier?,
|
||||
messageBackupsType: MessageBackupsType?,
|
||||
onEnableBackupsClick: () -> Unit,
|
||||
onChangeBackupsTypeClick: () -> Unit
|
||||
) {
|
||||
val messageBackupsType = if (messageBackupTier != null) getTierDetails(messageBackupTier) else null
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable(enabled = messageBackupTier != null, onClick = onChangeBackupsTypeClick)
|
||||
.clickable(enabled = messageBackupsType != null, onClick = onChangeBackupsTypeClick)
|
||||
.padding(horizontal = dimensionResource(id = R.dimen.core_ui__gutter))
|
||||
.padding(top = 16.dp, bottom = 14.dp)
|
||||
) {
|
||||
@@ -573,7 +582,7 @@ private fun getTextForFrequency(backupsFrequency: BackupFrequency): String {
|
||||
private fun RemoteBackupsSettingsContentPreview() {
|
||||
Previews.Preview {
|
||||
RemoteBackupsSettingsContent(
|
||||
messageBackupTier = null,
|
||||
messageBackupsType = null,
|
||||
lastBackupTimestamp = -1,
|
||||
canBackUpUsingCellular = false,
|
||||
backupsFrequency = BackupFrequency.MANUAL,
|
||||
@@ -591,7 +600,12 @@ private fun RemoteBackupsSettingsContentPreview() {
|
||||
private fun BackupTypeRowPreview() {
|
||||
Previews.Preview {
|
||||
BackupTypeRow(
|
||||
messageBackupTier = MessageBackupTier.PAID,
|
||||
messageBackupsType = MessageBackupsType(
|
||||
tier = MessageBackupTier.FREE,
|
||||
title = "Free",
|
||||
pricePerMonth = FiatMoney(BigDecimal.ZERO, Currency.getInstance("USD")),
|
||||
features = persistentListOf()
|
||||
),
|
||||
onChangeBackupsTypeClick = {},
|
||||
onEnableBackupsClick = {}
|
||||
)
|
||||
|
||||
@@ -7,10 +7,10 @@ package org.thoughtcrime.securesms.components.settings.app.chats.backups
|
||||
|
||||
import org.thoughtcrime.securesms.backup.v2.BackupFrequency
|
||||
import org.thoughtcrime.securesms.backup.v2.BackupV2Event
|
||||
import org.thoughtcrime.securesms.backup.v2.MessageBackupTier
|
||||
import org.thoughtcrime.securesms.backup.v2.ui.subscription.MessageBackupsType
|
||||
|
||||
data class RemoteBackupsSettingsState(
|
||||
val messageBackupsTier: MessageBackupTier? = null,
|
||||
val messageBackupsType: MessageBackupsType? = null,
|
||||
val canBackUpUsingCellular: Boolean = false,
|
||||
val backupSize: Long = 0,
|
||||
val backupsFrequency: BackupFrequency = BackupFrequency.DAILY,
|
||||
|
||||
@@ -8,7 +8,10 @@ package org.thoughtcrime.securesms.components.settings.app.chats.backups
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import kotlinx.coroutines.launch
|
||||
import org.thoughtcrime.securesms.backup.v2.BackupFrequency
|
||||
import org.thoughtcrime.securesms.backup.v2.BackupRepository
|
||||
import org.thoughtcrime.securesms.backup.v2.BackupV2Event
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies
|
||||
import org.thoughtcrime.securesms.jobs.BackupMessagesJob
|
||||
@@ -21,7 +24,7 @@ import org.thoughtcrime.securesms.service.MessageBackupListener
|
||||
class RemoteBackupsSettingsViewModel : ViewModel() {
|
||||
private val internalState = mutableStateOf(
|
||||
RemoteBackupsSettingsState(
|
||||
messageBackupsTier = SignalStore.backup().backupTier,
|
||||
messageBackupsType = null,
|
||||
lastBackupTimestamp = SignalStore.backup().lastBackupTime,
|
||||
backupSize = SignalStore.backup().totalBackupSize,
|
||||
backupsFrequency = SignalStore.backup().backupFrequency
|
||||
@@ -30,6 +33,10 @@ class RemoteBackupsSettingsViewModel : ViewModel() {
|
||||
|
||||
val state: State<RemoteBackupsSettingsState> = internalState
|
||||
|
||||
init {
|
||||
refresh()
|
||||
}
|
||||
|
||||
fun setCanBackUpUsingCellular(canBackUpUsingCellular: Boolean) {
|
||||
SignalStore.backup().backupWithCellular = canBackUpUsingCellular
|
||||
internalState.value = state.value.copy(canBackUpUsingCellular = canBackUpUsingCellular)
|
||||
@@ -51,12 +58,17 @@ class RemoteBackupsSettingsViewModel : ViewModel() {
|
||||
}
|
||||
|
||||
fun refresh() {
|
||||
internalState.value = state.value.copy(
|
||||
messageBackupsTier = SignalStore.backup().backupTier,
|
||||
lastBackupTimestamp = SignalStore.backup().lastBackupTime,
|
||||
backupSize = SignalStore.backup().totalBackupSize,
|
||||
backupsFrequency = SignalStore.backup().backupFrequency
|
||||
)
|
||||
viewModelScope.launch {
|
||||
val tier = SignalStore.backup().backupTier
|
||||
val backupType = if (tier != null) BackupRepository.getBackupsType(tier) else null
|
||||
|
||||
internalState.value = state.value.copy(
|
||||
messageBackupsType = backupType,
|
||||
lastBackupTimestamp = SignalStore.backup().lastBackupTime,
|
||||
backupSize = SignalStore.backup().totalBackupSize,
|
||||
backupsFrequency = SignalStore.backup().backupFrequency
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun turnOffAndDeleteBackups() {
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
|
||||
package org.thoughtcrime.securesms.components.settings.app.chats.backups.type
|
||||
|
||||
import android.content.Intent
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
@@ -19,19 +18,24 @@ import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import org.signal.core.ui.Previews
|
||||
import org.signal.core.ui.Rows
|
||||
import org.signal.core.ui.Scaffolds
|
||||
import org.signal.core.ui.SignalPreview
|
||||
import org.signal.core.util.money.FiatMoney
|
||||
import org.signal.donations.InAppPaymentType
|
||||
import org.signal.donations.PaymentSourceType
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.backup.v2.MessageBackupTier
|
||||
import org.thoughtcrime.securesms.backup.v2.ui.subscription.MessageBackupsFlowActivity
|
||||
import org.thoughtcrime.securesms.backup.v2.ui.subscription.getTierDetails
|
||||
import org.thoughtcrime.securesms.backup.v2.ui.subscription.MessageBackupsType
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.donate.CheckoutFlowActivity
|
||||
import org.thoughtcrime.securesms.compose.ComposeFragment
|
||||
import org.thoughtcrime.securesms.payments.FiatMoneyUtil
|
||||
import org.thoughtcrime.securesms.util.DateUtils
|
||||
import org.thoughtcrime.securesms.util.viewModel
|
||||
import java.math.BigDecimal
|
||||
import java.util.Currency
|
||||
import java.util.Locale
|
||||
|
||||
/**
|
||||
@@ -67,7 +71,7 @@ class BackupsTypeSettingsFragment : ComposeFragment() {
|
||||
}
|
||||
|
||||
override fun onChangeOrCancelSubscriptionClick() {
|
||||
startActivity(Intent(requireContext(), MessageBackupsFlowActivity::class.java))
|
||||
startActivity(CheckoutFlowActivity.createIntent(requireContext(), InAppPaymentType.RECURRING_BACKUP))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,7 +92,7 @@ private fun BackupsTypeSettingsContent(
|
||||
state: BackupsTypeSettingsState,
|
||||
contentCallbacks: ContentCallbacks
|
||||
) {
|
||||
if (state.backupsTier == null) {
|
||||
if (state.messageBackupsType == null) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -102,7 +106,7 @@ private fun BackupsTypeSettingsContent(
|
||||
) {
|
||||
item {
|
||||
BackupsTypeRow(
|
||||
backupsTier = state.backupsTier,
|
||||
messageBackupsType = state.messageBackupsType,
|
||||
nextRenewalTimestamp = state.nextRenewalTimestamp
|
||||
)
|
||||
}
|
||||
@@ -132,13 +136,9 @@ private fun BackupsTypeSettingsContent(
|
||||
|
||||
@Composable
|
||||
private fun BackupsTypeRow(
|
||||
backupsTier: MessageBackupTier,
|
||||
messageBackupsType: MessageBackupsType,
|
||||
nextRenewalTimestamp: Long
|
||||
) {
|
||||
val messageBackupsType = remember(backupsTier) {
|
||||
getTierDetails(backupsTier)
|
||||
}
|
||||
|
||||
val resources = LocalContext.current.resources
|
||||
val formattedAmount = remember(messageBackupsType.pricePerMonth) {
|
||||
FiatMoneyUtil.format(resources, messageBackupsType.pricePerMonth, FiatMoneyUtil.formatOptions().trimZerosAfterDecimal())
|
||||
@@ -191,7 +191,12 @@ private fun BackupsTypeSettingsContentPreview() {
|
||||
Previews.Preview {
|
||||
BackupsTypeSettingsContent(
|
||||
state = BackupsTypeSettingsState(
|
||||
backupsTier = MessageBackupTier.PAID
|
||||
messageBackupsType = MessageBackupsType(
|
||||
tier = MessageBackupTier.FREE,
|
||||
pricePerMonth = FiatMoney(BigDecimal.ZERO, Currency.getInstance("USD")),
|
||||
title = "Free",
|
||||
features = persistentListOf()
|
||||
)
|
||||
),
|
||||
contentCallbacks = object : ContentCallbacks {}
|
||||
)
|
||||
|
||||
@@ -7,11 +7,11 @@ package org.thoughtcrime.securesms.components.settings.app.chats.backups.type
|
||||
|
||||
import androidx.compose.runtime.Stable
|
||||
import org.signal.donations.PaymentSourceType
|
||||
import org.thoughtcrime.securesms.backup.v2.MessageBackupTier
|
||||
import org.thoughtcrime.securesms.backup.v2.ui.subscription.MessageBackupsType
|
||||
|
||||
@Stable
|
||||
data class BackupsTypeSettingsState(
|
||||
val backupsTier: MessageBackupTier? = null,
|
||||
val messageBackupsType: MessageBackupsType? = null,
|
||||
val paymentSourceType: PaymentSourceType = PaymentSourceType.Unknown,
|
||||
val nextRenewalTimestamp: Long = 0
|
||||
)
|
||||
|
||||
@@ -8,18 +8,26 @@ package org.thoughtcrime.securesms.components.settings.app.chats.backups.type
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import kotlinx.coroutines.launch
|
||||
import org.thoughtcrime.securesms.backup.v2.BackupRepository
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
|
||||
class BackupsTypeSettingsViewModel : ViewModel() {
|
||||
private val internalState = mutableStateOf(
|
||||
BackupsTypeSettingsState(
|
||||
backupsTier = SignalStore.backup().backupTier
|
||||
)
|
||||
)
|
||||
private val internalState = mutableStateOf(BackupsTypeSettingsState())
|
||||
|
||||
val state: State<BackupsTypeSettingsState> = internalState
|
||||
|
||||
init {
|
||||
refresh()
|
||||
}
|
||||
|
||||
fun refresh() {
|
||||
internalState.value = state.value.copy(backupsTier = SignalStore.backup().backupTier)
|
||||
viewModelScope.launch {
|
||||
val tier = SignalStore.backup().backupTier
|
||||
internalState.value = state.value.copy(
|
||||
messageBackupsType = if (tier != null) BackupRepository.getBackupsType(tier) else null
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ package org.thoughtcrime.securesms.components.settings.app.internal
|
||||
import android.content.Context
|
||||
import org.json.JSONObject
|
||||
import org.signal.core.util.concurrent.SignalExecutors
|
||||
import org.thoughtcrime.securesms.database.InAppPaymentTable
|
||||
import org.signal.donations.InAppPaymentType
|
||||
import org.thoughtcrime.securesms.database.MessageTable
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.database.model.RemoteMegaphoneRecord
|
||||
@@ -34,7 +34,7 @@ class InternalSettingsRepository(context: Context) {
|
||||
|
||||
fun enqueueSubscriptionRedemption() {
|
||||
SignalExecutors.BOUNDED.execute {
|
||||
val latest = SignalDatabase.inAppPayments.getByLatestEndOfPeriod(InAppPaymentTable.Type.RECURRING_DONATION)
|
||||
val latest = SignalDatabase.inAppPayments.getByLatestEndOfPeriod(InAppPaymentType.RECURRING_DONATION)
|
||||
if (latest != null) {
|
||||
InAppPaymentRecurringContextJob.createJobChain(latest).enqueue()
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package org.thoughtcrime.securesms.components.settings.app.subscription
|
||||
|
||||
import org.signal.donations.InAppPaymentType
|
||||
import org.signal.donations.PaymentSourceType
|
||||
import org.thoughtcrime.securesms.database.InAppPaymentTable
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.util.Environment
|
||||
import org.thoughtcrime.securesms.util.LocaleRemoteConfig
|
||||
@@ -24,7 +24,7 @@ object InAppDonations {
|
||||
return isCreditCardAvailable() || isPayPalAvailable() || isGooglePayAvailable() || isSEPADebitAvailable() || isIDEALAvailable()
|
||||
}
|
||||
|
||||
fun isPaymentSourceAvailable(paymentSourceType: PaymentSourceType, inAppPaymentType: InAppPaymentTable.Type): Boolean {
|
||||
fun isPaymentSourceAvailable(paymentSourceType: PaymentSourceType, inAppPaymentType: InAppPaymentType): Boolean {
|
||||
return when (paymentSourceType) {
|
||||
PaymentSourceType.PayPal -> isPayPalAvailableForDonateToSignalType(inAppPaymentType)
|
||||
PaymentSourceType.Stripe.CreditCard -> isCreditCardAvailable()
|
||||
@@ -35,12 +35,12 @@ object InAppDonations {
|
||||
}
|
||||
}
|
||||
|
||||
private fun isPayPalAvailableForDonateToSignalType(inAppPaymentType: InAppPaymentTable.Type): Boolean {
|
||||
private fun isPayPalAvailableForDonateToSignalType(inAppPaymentType: InAppPaymentType): Boolean {
|
||||
return when (inAppPaymentType) {
|
||||
InAppPaymentTable.Type.UNKNOWN -> error("Unsupported type UNKNOWN")
|
||||
InAppPaymentTable.Type.ONE_TIME_DONATION, InAppPaymentTable.Type.ONE_TIME_GIFT -> RemoteConfig.paypalOneTimeDonations
|
||||
InAppPaymentTable.Type.RECURRING_DONATION -> RemoteConfig.paypalRecurringDonations
|
||||
InAppPaymentTable.Type.RECURRING_BACKUP -> RemoteConfig.messageBackups && RemoteConfig.paypalRecurringDonations
|
||||
InAppPaymentType.UNKNOWN -> error("Unsupported type UNKNOWN")
|
||||
InAppPaymentType.ONE_TIME_DONATION, InAppPaymentType.ONE_TIME_GIFT -> RemoteConfig.paypalOneTimeDonations
|
||||
InAppPaymentType.RECURRING_DONATION -> RemoteConfig.paypalRecurringDonations
|
||||
InAppPaymentType.RECURRING_BACKUP -> RemoteConfig.messageBackups && RemoteConfig.paypalRecurringDonations
|
||||
} && !LocaleRemoteConfig.isPayPalDisabled()
|
||||
}
|
||||
|
||||
@@ -83,15 +83,15 @@ object InAppDonations {
|
||||
* Whether the user is in a region which supports SEPA Debit transfers, based off local phone number
|
||||
* and donation type.
|
||||
*/
|
||||
fun isSEPADebitAvailableForDonateToSignalType(inAppPaymentType: InAppPaymentTable.Type): Boolean {
|
||||
return inAppPaymentType != InAppPaymentTable.Type.ONE_TIME_GIFT && isSEPADebitAvailable()
|
||||
fun isSEPADebitAvailableForDonateToSignalType(inAppPaymentType: InAppPaymentType): Boolean {
|
||||
return inAppPaymentType != InAppPaymentType.ONE_TIME_GIFT && isSEPADebitAvailable()
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the user is in a region which suports IDEAL transfers, based off local phone number and
|
||||
* donation type
|
||||
*/
|
||||
fun isIDEALAvailbleForDonateToSignalType(inAppPaymentType: InAppPaymentTable.Type): Boolean {
|
||||
return inAppPaymentType != InAppPaymentTable.Type.ONE_TIME_GIFT && isIDEALAvailable()
|
||||
fun isIDEALAvailbleForDonateToSignalType(inAppPaymentType: InAppPaymentType): Boolean {
|
||||
return inAppPaymentType != InAppPaymentType.ONE_TIME_GIFT && isIDEALAvailable()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import android.os.Parcelable
|
||||
import io.reactivex.rxjava3.subjects.Subject
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
interface DonationPaymentComponent {
|
||||
interface InAppPaymentComponent {
|
||||
val stripeRepository: StripeRepository
|
||||
val googlePayResultPublisher: Subject<GooglePayResult>
|
||||
|
||||
@@ -14,7 +14,9 @@ import io.reactivex.rxjava3.core.Observable
|
||||
import io.reactivex.rxjava3.core.Single
|
||||
import io.reactivex.rxjava3.processors.PublishProcessor
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||
import org.signal.core.util.concurrent.SignalExecutors
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.signal.donations.InAppPaymentType
|
||||
import org.signal.donations.PaymentSourceType
|
||||
import org.signal.donations.StripeDeclineCode
|
||||
import org.signal.donations.StripeFailureCode
|
||||
@@ -38,6 +40,8 @@ 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.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.storage.StorageSyncHelper
|
||||
import org.thoughtcrime.securesms.util.Util
|
||||
import org.whispersystems.signalservice.api.subscriptions.ActiveSubscription
|
||||
import org.whispersystems.signalservice.api.subscriptions.SubscriberId
|
||||
@@ -167,9 +171,9 @@ object InAppPaymentsRepository {
|
||||
*/
|
||||
fun resolveJobQueueKey(inAppPayment: InAppPaymentTable.InAppPayment): String {
|
||||
return when (inAppPayment.type) {
|
||||
InAppPaymentTable.Type.UNKNOWN -> error("Unsupported type UNKNOWN.")
|
||||
InAppPaymentTable.Type.ONE_TIME_GIFT, InAppPaymentTable.Type.ONE_TIME_DONATION -> "$JOB_PREFIX${inAppPayment.id.serialize()}"
|
||||
InAppPaymentTable.Type.RECURRING_DONATION, InAppPaymentTable.Type.RECURRING_BACKUP -> "$JOB_PREFIX${inAppPayment.type.code}"
|
||||
InAppPaymentType.UNKNOWN -> error("Unsupported type UNKNOWN.")
|
||||
InAppPaymentType.ONE_TIME_GIFT, InAppPaymentType.ONE_TIME_DONATION -> "$JOB_PREFIX${inAppPayment.id.serialize()}"
|
||||
InAppPaymentType.RECURRING_DONATION, InAppPaymentType.RECURRING_BACKUP -> "$JOB_PREFIX${inAppPayment.type.code}"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -196,13 +200,13 @@ object InAppPaymentsRepository {
|
||||
/**
|
||||
* Maps a payment type into a request code for grabbing a Google Pay token.
|
||||
*/
|
||||
fun getGooglePayRequestCode(inAppPaymentType: InAppPaymentTable.Type): Int {
|
||||
fun getGooglePayRequestCode(inAppPaymentType: InAppPaymentType): Int {
|
||||
return when (inAppPaymentType) {
|
||||
InAppPaymentTable.Type.UNKNOWN -> error("Unsupported type UNKNOWN")
|
||||
InAppPaymentTable.Type.ONE_TIME_GIFT -> 16143
|
||||
InAppPaymentTable.Type.ONE_TIME_DONATION -> 16141
|
||||
InAppPaymentTable.Type.RECURRING_DONATION -> 16142
|
||||
InAppPaymentTable.Type.RECURRING_BACKUP -> 16144
|
||||
InAppPaymentType.UNKNOWN -> error("Unsupported type UNKNOWN")
|
||||
InAppPaymentType.ONE_TIME_GIFT -> 16143
|
||||
InAppPaymentType.ONE_TIME_DONATION -> 16141
|
||||
InAppPaymentType.RECURRING_DONATION -> 16142
|
||||
InAppPaymentType.RECURRING_BACKUP -> 16144
|
||||
}
|
||||
}
|
||||
|
||||
@@ -210,14 +214,14 @@ object InAppPaymentsRepository {
|
||||
* Converts an error source to a persistable type. For types that don't map,
|
||||
* UNKNOWN is returned.
|
||||
*/
|
||||
fun DonationErrorSource.toInAppPaymentType(): InAppPaymentTable.Type {
|
||||
fun DonationErrorSource.toInAppPaymentType(): InAppPaymentType {
|
||||
return when (this) {
|
||||
DonationErrorSource.ONE_TIME -> InAppPaymentTable.Type.ONE_TIME_DONATION
|
||||
DonationErrorSource.MONTHLY -> InAppPaymentTable.Type.RECURRING_DONATION
|
||||
DonationErrorSource.GIFT -> InAppPaymentTable.Type.ONE_TIME_GIFT
|
||||
DonationErrorSource.GIFT_REDEMPTION -> InAppPaymentTable.Type.UNKNOWN
|
||||
DonationErrorSource.KEEP_ALIVE -> InAppPaymentTable.Type.UNKNOWN
|
||||
DonationErrorSource.UNKNOWN -> InAppPaymentTable.Type.UNKNOWN
|
||||
DonationErrorSource.ONE_TIME -> InAppPaymentType.ONE_TIME_DONATION
|
||||
DonationErrorSource.MONTHLY -> InAppPaymentType.RECURRING_DONATION
|
||||
DonationErrorSource.GIFT -> InAppPaymentType.ONE_TIME_GIFT
|
||||
DonationErrorSource.GIFT_REDEMPTION -> InAppPaymentType.UNKNOWN
|
||||
DonationErrorSource.KEEP_ALIVE -> InAppPaymentType.UNKNOWN
|
||||
DonationErrorSource.UNKNOWN -> InAppPaymentType.UNKNOWN
|
||||
}
|
||||
}
|
||||
|
||||
@@ -249,6 +253,28 @@ object InAppPaymentsRepository {
|
||||
}
|
||||
}
|
||||
|
||||
fun InAppPaymentType.toErrorSource(): DonationErrorSource {
|
||||
return when (this) {
|
||||
InAppPaymentType.UNKNOWN -> DonationErrorSource.UNKNOWN
|
||||
InAppPaymentType.ONE_TIME_GIFT -> DonationErrorSource.GIFT
|
||||
InAppPaymentType.ONE_TIME_DONATION -> DonationErrorSource.ONE_TIME
|
||||
InAppPaymentType.RECURRING_DONATION -> DonationErrorSource.MONTHLY
|
||||
InAppPaymentType.RECURRING_BACKUP -> DonationErrorSource.UNKNOWN // TODO [message-backups] error handling
|
||||
}
|
||||
}
|
||||
|
||||
fun InAppPaymentType.toSubscriberType(): InAppPaymentSubscriberRecord.Type? {
|
||||
return when (this) {
|
||||
InAppPaymentType.RECURRING_BACKUP -> InAppPaymentSubscriberRecord.Type.BACKUP
|
||||
InAppPaymentType.RECURRING_DONATION -> InAppPaymentSubscriberRecord.Type.DONATION
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
fun InAppPaymentType.requireSubscriberType(): InAppPaymentSubscriberRecord.Type {
|
||||
return requireNotNull(toSubscriberType())
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts network ChargeFailure objects into the form we can persist in the database.
|
||||
*/
|
||||
@@ -375,6 +401,7 @@ object InAppPaymentsRepository {
|
||||
@WorkerThread
|
||||
fun getSubscriber(type: InAppPaymentSubscriberRecord.Type): InAppPaymentSubscriberRecord? {
|
||||
val currency = SignalStore.donationsValues().getSubscriptionCurrency(type)
|
||||
Log.d(TAG, "Attempting to retrieve subscriber of type $type for ${currency.currencyCode}")
|
||||
|
||||
return getSubscriber(currency, type)
|
||||
}
|
||||
@@ -408,13 +435,13 @@ object InAppPaymentsRepository {
|
||||
/**
|
||||
* Emits a stream of status updates for donations of the given type. Only One-time donations and recurring donations are currently supported.
|
||||
*/
|
||||
fun observeInAppPaymentRedemption(type: InAppPaymentTable.Type): Observable<DonationRedemptionJobStatus> {
|
||||
fun observeInAppPaymentRedemption(type: InAppPaymentType): Observable<DonationRedemptionJobStatus> {
|
||||
val jobStatusObservable: Observable<DonationRedemptionJobStatus> = when (type) {
|
||||
InAppPaymentTable.Type.UNKNOWN -> Observable.empty()
|
||||
InAppPaymentTable.Type.ONE_TIME_GIFT -> Observable.empty()
|
||||
InAppPaymentTable.Type.ONE_TIME_DONATION -> DonationRedemptionJobWatcher.watchOneTimeRedemption()
|
||||
InAppPaymentTable.Type.RECURRING_DONATION -> DonationRedemptionJobWatcher.watchSubscriptionRedemption()
|
||||
InAppPaymentTable.Type.RECURRING_BACKUP -> Observable.empty()
|
||||
InAppPaymentType.UNKNOWN -> Observable.empty()
|
||||
InAppPaymentType.ONE_TIME_GIFT -> Observable.empty()
|
||||
InAppPaymentType.ONE_TIME_DONATION -> DonationRedemptionJobWatcher.watchOneTimeRedemption()
|
||||
InAppPaymentType.RECURRING_DONATION -> DonationRedemptionJobWatcher.watchSubscriptionRedemption()
|
||||
InAppPaymentType.RECURRING_BACKUP -> Observable.empty()
|
||||
}
|
||||
|
||||
val fromDatabase: Observable<DonationRedemptionJobStatus> = Observable.create { emitter ->
|
||||
@@ -467,6 +494,17 @@ object InAppPaymentsRepository {
|
||||
.distinctUntilChanged()
|
||||
}
|
||||
|
||||
fun scheduleSyncForAccountRecordChange() {
|
||||
SignalExecutors.BOUNDED.execute {
|
||||
scheduleSyncForAccountRecordChangeSync()
|
||||
}
|
||||
}
|
||||
|
||||
private fun scheduleSyncForAccountRecordChangeSync() {
|
||||
SignalDatabase.recipients.markNeedsSync(Recipient.self().id)
|
||||
StorageSyncHelper.scheduleSyncForDataChange()
|
||||
}
|
||||
|
||||
private fun InAppPaymentTable.InAppPayment.toPendingOneTimeDonation(): PendingOneTimeDonation? {
|
||||
if (type.recurring) {
|
||||
return null
|
||||
|
||||
@@ -6,6 +6,8 @@ import io.reactivex.rxjava3.schedulers.Schedulers
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.signal.donations.PaymentSourceType
|
||||
import org.thoughtcrime.securesms.badges.Badges
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.InAppPaymentsRepository.requireSubscriberType
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.InAppPaymentsRepository.toErrorSource
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.InAppPaymentsRepository.toPaymentSourceType
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.errors.DonationError
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.errors.DonationError.BadgeRedemptionError
|
||||
@@ -96,7 +98,7 @@ class RecurringInAppPaymentRepository(private val donationsService: DonationsSer
|
||||
}
|
||||
|
||||
fun ensureSubscriberId(subscriberType: InAppPaymentSubscriberRecord.Type, isRotation: Boolean = false): Completable {
|
||||
Log.d(TAG, "Ensuring SubscriberId exists on Signal service {isRotation?$isRotation}...", true)
|
||||
Log.d(TAG, "Ensuring SubscriberId for type $subscriberType exists on Signal service {isRotation?$isRotation}...", true)
|
||||
val subscriberId: SubscriberId = if (isRotation) {
|
||||
SubscriberId.generate()
|
||||
} else {
|
||||
@@ -138,7 +140,12 @@ class RecurringInAppPaymentRepository(private val donationsService: DonationsSer
|
||||
.subscribeOn(Schedulers.io())
|
||||
.flatMap(ServiceResponse<EmptyResponse>::flattenResult)
|
||||
.ignoreElement()
|
||||
.doOnComplete { Log.d(TAG, "Cancelled active subscription.", true) }
|
||||
.doOnComplete {
|
||||
Log.d(TAG, "Cancelled active subscription.", true)
|
||||
SignalStore.donationsValues().updateLocalStateForManualCancellation(subscriberType)
|
||||
MultiDeviceSubscriptionSyncRequestJob.enqueue()
|
||||
InAppPaymentsRepository.scheduleSyncForAccountRecordChange()
|
||||
}
|
||||
}
|
||||
|
||||
fun cancelActiveSubscriptionIfNecessary(subscriberType: InAppPaymentSubscriberRecord.Type): Completable {
|
||||
|
||||
@@ -5,14 +5,15 @@ import android.content.Intent
|
||||
import io.reactivex.rxjava3.core.Completable
|
||||
import io.reactivex.rxjava3.core.Single
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||
import org.signal.core.util.concurrent.SignalExecutors
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.signal.core.util.money.FiatMoney
|
||||
import org.signal.donations.GooglePayApi
|
||||
import org.signal.donations.InAppPaymentType
|
||||
import org.signal.donations.PaymentSourceType
|
||||
import org.signal.donations.StripeApi
|
||||
import org.signal.donations.StripeIntentAccessor
|
||||
import org.signal.donations.json.StripeIntentStatus
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.InAppPaymentsRepository.requireSubscriberType
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.InAppPaymentsRepository.toPaymentMethodType
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.errors.DonationError
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.errors.DonationError.OneTimeDonationError
|
||||
@@ -22,7 +23,6 @@ import org.thoughtcrime.securesms.database.model.InAppPaymentSubscriberRecord
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.thoughtcrime.securesms.storage.StorageSyncHelper
|
||||
import org.thoughtcrime.securesms.util.Environment
|
||||
import org.whispersystems.signalservice.api.subscriptions.StripeClientSecret
|
||||
import org.whispersystems.signalservice.internal.EmptyResponse
|
||||
@@ -46,8 +46,7 @@ import org.whispersystems.signalservice.internal.ServiceResponse
|
||||
* 1. Confirm the PaymentIntent via the Stripe API
|
||||
*/
|
||||
class StripeRepository(
|
||||
activity: Activity,
|
||||
private val subscriberType: InAppPaymentSubscriberRecord.Type = InAppPaymentSubscriberRecord.Type.DONATION
|
||||
activity: Activity
|
||||
) : StripeApi.PaymentIntentFetcher, StripeApi.SetupIntentHelper {
|
||||
|
||||
private val googlePayApi = GooglePayApi(activity, StripeApi.Gateway(Environment.Donations.STRIPE_CONFIGURATION), Environment.Donations.GOOGLE_PAY_CONFIGURATION)
|
||||
@@ -58,17 +57,6 @@ class StripeRepository(
|
||||
return googlePayApi.queryIsReadyToPay()
|
||||
}
|
||||
|
||||
fun scheduleSyncForAccountRecordChange() {
|
||||
SignalExecutors.BOUNDED.execute {
|
||||
scheduleSyncForAccountRecordChangeSync()
|
||||
}
|
||||
}
|
||||
|
||||
private fun scheduleSyncForAccountRecordChangeSync() {
|
||||
SignalDatabase.recipients.markNeedsSync(Recipient.self().id)
|
||||
StorageSyncHelper.scheduleSyncForDataChange()
|
||||
}
|
||||
|
||||
fun requestTokenFromGooglePay(price: FiatMoney, label: String, requestCode: Int) {
|
||||
Log.d(TAG, "Requesting a token from google pay...")
|
||||
googlePayApi.requestPayment(price, label, requestCode)
|
||||
@@ -118,11 +106,12 @@ class StripeRepository(
|
||||
}
|
||||
|
||||
fun createAndConfirmSetupIntent(
|
||||
inAppPaymentType: InAppPaymentType,
|
||||
paymentSource: StripeApi.PaymentSource,
|
||||
paymentSourceType: PaymentSourceType.Stripe
|
||||
): Single<StripeApi.Secure3DSAction> {
|
||||
Log.d(TAG, "Continuing subscription setup...", true)
|
||||
return stripeApi.createSetupIntent(paymentSourceType)
|
||||
return stripeApi.createSetupIntent(inAppPaymentType, paymentSourceType)
|
||||
.flatMap { result ->
|
||||
Log.d(TAG, "Retrieved SetupIntent, confirming...", true)
|
||||
stripeApi.confirmSetupIntent(paymentSource, result.setupIntent)
|
||||
@@ -169,7 +158,7 @@ class StripeRepository(
|
||||
* it means that the PaymentMethod is already tied to a PayPal account. We can retry in this
|
||||
* situation by simply deleting the old subscriber id on the service and replacing it.
|
||||
*/
|
||||
private fun createPaymentMethod(paymentSourceType: PaymentSourceType.Stripe, retryOn409: Boolean = true): Single<StripeClientSecret> {
|
||||
private fun createPaymentMethod(subscriberType: InAppPaymentSubscriberRecord.Type, paymentSourceType: PaymentSourceType.Stripe, retryOn409: Boolean = true): Single<StripeClientSecret> {
|
||||
return Single.fromCallable { InAppPaymentsRepository.requireSubscriber(subscriberType) }
|
||||
.flatMap {
|
||||
Single.fromCallable {
|
||||
@@ -180,16 +169,16 @@ class StripeRepository(
|
||||
}
|
||||
.flatMap { serviceResponse ->
|
||||
if (retryOn409 && serviceResponse.status == 409) {
|
||||
recurringInAppPaymentRepository.rotateSubscriberId(subscriberType).andThen(createPaymentMethod(paymentSourceType, retryOn409 = false))
|
||||
recurringInAppPaymentRepository.rotateSubscriberId(subscriberType).andThen(createPaymentMethod(subscriberType, paymentSourceType, retryOn409 = false))
|
||||
} else {
|
||||
serviceResponse.flattenResult()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun fetchSetupIntent(sourceType: PaymentSourceType.Stripe): Single<StripeIntentAccessor> {
|
||||
override fun fetchSetupIntent(inAppPaymentType: InAppPaymentType, sourceType: PaymentSourceType.Stripe): Single<StripeIntentAccessor> {
|
||||
Log.d(TAG, "Fetching setup intent from Signal service...")
|
||||
return createPaymentMethod(sourceType)
|
||||
return createPaymentMethod(inAppPaymentType.requireSubscriberType(), sourceType)
|
||||
.map {
|
||||
StripeIntentAccessor(
|
||||
objectType = StripeIntentAccessor.ObjectType.SETUP_INTENT,
|
||||
@@ -231,6 +220,7 @@ class StripeRepository(
|
||||
fun setDefaultPaymentMethod(
|
||||
paymentMethodId: String,
|
||||
setupIntentId: String,
|
||||
subscriberType: InAppPaymentSubscriberRecord.Type,
|
||||
paymentSourceType: PaymentSourceType
|
||||
): Completable {
|
||||
return Single.fromCallable {
|
||||
|
||||
@@ -5,9 +5,8 @@ import org.thoughtcrime.securesms.components.settings.DSLConfiguration
|
||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsAdapter
|
||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsBottomSheetFragment
|
||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsText
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.DonationPaymentComponent
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.InAppPaymentsRepository
|
||||
import org.thoughtcrime.securesms.components.settings.configure
|
||||
import org.thoughtcrime.securesms.util.fragments.requireListener
|
||||
import java.util.Locale
|
||||
|
||||
/**
|
||||
@@ -15,8 +14,6 @@ import java.util.Locale
|
||||
*/
|
||||
class SetCurrencyFragment : DSLSettingsBottomSheetFragment() {
|
||||
|
||||
private lateinit var donationPaymentComponent: DonationPaymentComponent
|
||||
|
||||
private val viewModel: SetCurrencyViewModel by viewModels(
|
||||
factoryProducer = {
|
||||
val args = SetCurrencyFragmentArgs.fromBundle(requireArguments())
|
||||
@@ -25,8 +22,6 @@ class SetCurrencyFragment : DSLSettingsBottomSheetFragment() {
|
||||
)
|
||||
|
||||
override fun bindAdapter(adapter: DSLSettingsAdapter) {
|
||||
donationPaymentComponent = requireListener()
|
||||
|
||||
viewModel.state.observe(viewLifecycleOwner) { state ->
|
||||
adapter.submitList(getConfiguration(state).toMappingModelList())
|
||||
}
|
||||
@@ -40,7 +35,7 @@ class SetCurrencyFragment : DSLSettingsBottomSheetFragment() {
|
||||
summary = DSLSettingsText.from(currency.currencyCode),
|
||||
onClick = {
|
||||
viewModel.setSelectedCurrency(currency.currencyCode)
|
||||
donationPaymentComponent.stripeRepository.scheduleSyncForAccountRecordChange()
|
||||
InAppPaymentsRepository.scheduleSyncForAccountRecordChange()
|
||||
dismissAllowingStateLoss()
|
||||
}
|
||||
)
|
||||
|
||||
@@ -4,9 +4,10 @@ import androidx.annotation.VisibleForTesting
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import org.signal.donations.InAppPaymentType
|
||||
import org.thoughtcrime.securesms.BuildConfig
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.InAppPaymentsRepository
|
||||
import org.thoughtcrime.securesms.database.InAppPaymentTable
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.InAppPaymentsRepository.requireSubscriberType
|
||||
import org.thoughtcrime.securesms.database.model.InAppPaymentSubscriberRecord
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.InAppPaymentData
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
@@ -16,7 +17,7 @@ import java.util.Currency
|
||||
import java.util.Locale
|
||||
|
||||
class SetCurrencyViewModel(
|
||||
private val inAppPaymentType: InAppPaymentTable.Type,
|
||||
private val inAppPaymentType: InAppPaymentType,
|
||||
supportedCurrencyCodes: List<String>
|
||||
) : ViewModel() {
|
||||
|
||||
@@ -89,7 +90,7 @@ class SetCurrencyViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
class Factory(private val inAppPaymentType: InAppPaymentTable.Type, private val supportedCurrencyCodes: List<String>) : ViewModelProvider.Factory {
|
||||
class Factory(private val inAppPaymentType: InAppPaymentType, private val supportedCurrencyCodes: List<String>) : ViewModelProvider.Factory {
|
||||
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
||||
return modelClass.cast(SetCurrencyViewModel(inAppPaymentType, supportedCurrencyCodes))!!
|
||||
}
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2024 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.components.settings.app.subscription.donate
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.navigation.navArgs
|
||||
import io.reactivex.rxjava3.subjects.PublishSubject
|
||||
import io.reactivex.rxjava3.subjects.Subject
|
||||
import org.signal.donations.InAppPaymentType
|
||||
import org.thoughtcrime.securesms.components.FragmentWrapperActivity
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.InAppPaymentComponent
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.StripeRepository
|
||||
|
||||
/**
|
||||
* Home base for all checkout flows.
|
||||
*/
|
||||
class CheckoutFlowActivity : FragmentWrapperActivity(), InAppPaymentComponent {
|
||||
|
||||
companion object {
|
||||
fun createIntent(context: Context, inAppPaymentType: InAppPaymentType): Intent {
|
||||
return Intent(context, CheckoutFlowActivity::class.java).putExtras(
|
||||
CheckoutFlowActivityArgs.Builder(inAppPaymentType).build().toBundle()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override val stripeRepository: StripeRepository by lazy { StripeRepository(this) }
|
||||
override val googlePayResultPublisher: Subject<InAppPaymentComponent.GooglePayResult> = PublishSubject.create()
|
||||
|
||||
private val args by navArgs<CheckoutFlowActivityArgs>()
|
||||
|
||||
override fun getFragment(): Fragment {
|
||||
return CheckoutNavHostFragment.create(args.inAppPaymentType)
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
googlePayResultPublisher.onNext(InAppPaymentComponent.GooglePayResult(requestCode, resultCode, data))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright 2024 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.components.settings.app.subscription.donate
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.navigation.fragment.NavHostFragment
|
||||
import org.signal.core.util.getSerializableCompat
|
||||
import org.signal.donations.InAppPaymentType
|
||||
import org.thoughtcrime.securesms.R
|
||||
|
||||
class CheckoutNavHostFragment : NavHostFragment() {
|
||||
|
||||
companion object {
|
||||
private const val ARG_TYPE = "host_in_app_payment_type"
|
||||
|
||||
@JvmStatic
|
||||
fun create(inAppPaymentType: InAppPaymentType): CheckoutNavHostFragment {
|
||||
val actual = CheckoutNavHostFragment()
|
||||
actual.arguments = bundleOf(ARG_TYPE to inAppPaymentType)
|
||||
|
||||
return actual
|
||||
}
|
||||
}
|
||||
|
||||
private val inAppPaymentType: InAppPaymentType
|
||||
get() = requireArguments().getSerializableCompat(ARG_TYPE, InAppPaymentType::class.java)!!
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
if (savedInstanceState == null) {
|
||||
val navGraph = navController.navInflater.inflate(R.navigation.checkout)
|
||||
navGraph.setStartDestination(
|
||||
when (inAppPaymentType) {
|
||||
InAppPaymentType.UNKNOWN -> error("Unsupported start destination")
|
||||
InAppPaymentType.ONE_TIME_GIFT -> R.id.giftFlowStartFragment
|
||||
InAppPaymentType.ONE_TIME_DONATION, InAppPaymentType.RECURRING_DONATION -> R.id.donateToSignalFragment
|
||||
InAppPaymentType.RECURRING_BACKUP -> R.id.messageBackupsFlowFragment
|
||||
}
|
||||
)
|
||||
|
||||
val startBundle = when (inAppPaymentType) {
|
||||
InAppPaymentType.UNKNOWN -> error("Unknown payment type")
|
||||
InAppPaymentType.ONE_TIME_GIFT, InAppPaymentType.RECURRING_BACKUP -> null
|
||||
InAppPaymentType.ONE_TIME_DONATION, InAppPaymentType.RECURRING_DONATION -> DonateToSignalFragmentArgs.Builder(inAppPaymentType).build().toBundle()
|
||||
}
|
||||
|
||||
navController.setGraph(navGraph, startBundle)
|
||||
}
|
||||
|
||||
super.onCreate(savedInstanceState)
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,10 @@
|
||||
package org.thoughtcrime.securesms.components.settings.app.subscription.donate
|
||||
|
||||
import org.signal.donations.InAppPaymentType
|
||||
import org.thoughtcrime.securesms.database.InAppPaymentTable
|
||||
|
||||
sealed class DonateToSignalAction {
|
||||
data class DisplayCurrencySelectionDialog(val inAppPaymentType: InAppPaymentTable.Type, val supportedCurrencies: List<String>) : DonateToSignalAction()
|
||||
data class DisplayCurrencySelectionDialog(val inAppPaymentType: InAppPaymentType, val supportedCurrencies: List<String>) : DonateToSignalAction()
|
||||
data class DisplayGatewaySelectorDialog(val inAppPayment: InAppPaymentTable.InAppPayment) : DonateToSignalAction()
|
||||
object CancelSubscription : DonateToSignalAction()
|
||||
data class UpdateSubscription(val inAppPayment: InAppPaymentTable.InAppPayment, val isLongRunning: Boolean) : DonateToSignalAction()
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
package org.thoughtcrime.securesms.components.settings.app.subscription.donate
|
||||
|
||||
import android.content.Intent
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.navigation.fragment.NavHostFragment
|
||||
import io.reactivex.rxjava3.subjects.PublishSubject
|
||||
import io.reactivex.rxjava3.subjects.Subject
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.components.FragmentWrapperActivity
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.DonationPaymentComponent
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.StripeRepository
|
||||
import org.thoughtcrime.securesms.database.InAppPaymentTable
|
||||
|
||||
/**
|
||||
* Activity wrapper for donate to signal screen. An activity is needed because Google Pay uses the
|
||||
* activity [DonateToSignalActivity.startActivityForResult] flow that would be missed by a parent fragment.
|
||||
*/
|
||||
class DonateToSignalActivity : FragmentWrapperActivity(), DonationPaymentComponent {
|
||||
|
||||
override val stripeRepository: StripeRepository by lazy { StripeRepository(this) }
|
||||
override val googlePayResultPublisher: Subject<DonationPaymentComponent.GooglePayResult> = PublishSubject.create()
|
||||
|
||||
override fun getFragment(): Fragment {
|
||||
return NavHostFragment.create(R.navigation.donate_to_signal, DonateToSignalFragmentArgs.Builder(InAppPaymentTable.Type.ONE_TIME_DONATION).build().toBundle())
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
googlePayResultPublisher.onNext(DonationPaymentComponent.GooglePayResult(requestCode, resultCode, data))
|
||||
}
|
||||
}
|
||||
@@ -5,19 +5,24 @@ import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.setFragmentResultListener
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.navigation.fragment.NavHostFragment
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.navigation.fragment.navArgs
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.airbnb.lottie.LottieAnimationView
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import org.signal.core.util.concurrent.LifecycleDisposable
|
||||
import org.signal.core.util.dp
|
||||
import org.signal.core.util.getParcelableCompat
|
||||
import org.signal.core.util.getSerializableCompat
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.signal.core.util.money.FiatMoney
|
||||
import org.signal.donations.InAppPaymentType
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.badges.Badges
|
||||
import org.thoughtcrime.securesms.badges.models.BadgePreview
|
||||
@@ -28,13 +33,16 @@ import org.thoughtcrime.securesms.components.settings.DSLConfiguration
|
||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment
|
||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsText
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.boost.Boost
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.donate.gateway.GatewaySelectorBottomSheet
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.models.CurrencySelection
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.models.NetworkFailure
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.thanks.ThanksForYourSupportBottomSheetDialogFragment
|
||||
import org.thoughtcrime.securesms.components.settings.configure
|
||||
import org.thoughtcrime.securesms.database.InAppPaymentTable
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.InAppPaymentData
|
||||
import org.thoughtcrime.securesms.databinding.DonateToSignalFragmentBinding
|
||||
import org.thoughtcrime.securesms.payments.FiatMoneyUtil
|
||||
import org.thoughtcrime.securesms.payments.currency.CurrencyUtil
|
||||
import org.thoughtcrime.securesms.subscription.Subscription
|
||||
import org.thoughtcrime.securesms.util.Material3OnScrollHelper
|
||||
import org.thoughtcrime.securesms.util.Projection
|
||||
@@ -42,6 +50,7 @@ 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.math.BigDecimal
|
||||
import java.util.Currency
|
||||
|
||||
/**
|
||||
@@ -51,8 +60,8 @@ class DonateToSignalFragment :
|
||||
DSLSettingsFragment(
|
||||
layoutId = R.layout.donate_to_signal_fragment
|
||||
),
|
||||
DonationCheckoutDelegate.Callback,
|
||||
ThanksForYourSupportBottomSheetDialogFragment.Callback {
|
||||
ThanksForYourSupportBottomSheetDialogFragment.Callback,
|
||||
DonationCheckoutDelegate.Callback {
|
||||
|
||||
companion object {
|
||||
private val TAG = Log.tag(DonateToSignalFragment::class.java)
|
||||
@@ -61,17 +70,19 @@ class DonateToSignalFragment :
|
||||
class Dialog : WrapperDialogFragment() {
|
||||
|
||||
override fun getWrappedFragment(): Fragment {
|
||||
return NavHostFragment.create(
|
||||
R.navigation.donate_to_signal,
|
||||
arguments
|
||||
return CheckoutNavHostFragment.create(
|
||||
requireArguments().getSerializableCompat(ARG, InAppPaymentType::class.java)!!
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private const val ARG = "in_app_payment_type"
|
||||
|
||||
@JvmStatic
|
||||
fun create(inAppPaymentType: InAppPaymentTable.Type): DialogFragment {
|
||||
fun create(inAppPaymentType: InAppPaymentType): DialogFragment {
|
||||
return Dialog().apply {
|
||||
arguments = DonateToSignalFragmentArgs.Builder(inAppPaymentType).build().toBundle()
|
||||
arguments = bundleOf(ARG to inAppPaymentType)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -85,8 +96,6 @@ class DonateToSignalFragment :
|
||||
private val disposables = LifecycleDisposable()
|
||||
private val binding by ViewBinderDelegate(DonateToSignalFragmentBinding::bind)
|
||||
|
||||
private var donationCheckoutDelegate: DonationCheckoutDelegate? = null
|
||||
|
||||
private val supportTechSummary: CharSequence by lazy {
|
||||
SpannableStringBuilder(SpanUtil.color(ContextCompat.getColor(requireContext(), R.color.signal_colorOnSurfaceVariant), requireContext().getString(R.string.DonateToSignalFragment__private_messaging)))
|
||||
.append(" ")
|
||||
@@ -109,11 +118,7 @@ class DonateToSignalFragment :
|
||||
}
|
||||
|
||||
override fun bindAdapter(adapter: MappingAdapter) {
|
||||
donationCheckoutDelegate = DonationCheckoutDelegate(
|
||||
this,
|
||||
this,
|
||||
viewModel.inAppPaymentId
|
||||
)
|
||||
val checkoutDelegate = DonationCheckoutDelegate(this, this, viewModel.inAppPaymentId)
|
||||
|
||||
val recyclerView = this.recyclerView!!
|
||||
recyclerView.overScrollMode = RecyclerView.OVER_SCROLL_IF_CONTENT_SCROLLS
|
||||
@@ -137,11 +142,20 @@ class DonateToSignalFragment :
|
||||
CurrencySelection.register(adapter)
|
||||
DonationPillToggle.register(adapter)
|
||||
|
||||
setFragmentResultListener(GatewaySelectorBottomSheet.REQUEST_KEY) { _, bundle ->
|
||||
if (bundle.containsKey(GatewaySelectorBottomSheet.FAILURE_KEY)) {
|
||||
showSepaEuroMaximumDialog(FiatMoney(bundle.getSerializable(GatewaySelectorBottomSheet.SEPA_EURO_MAX) as BigDecimal, CurrencyUtil.EURO))
|
||||
} else {
|
||||
val inAppPayment: InAppPaymentTable.InAppPayment = bundle.getParcelableCompat(GatewaySelectorBottomSheet.REQUEST_KEY, InAppPaymentTable.InAppPayment::class.java)!!
|
||||
checkoutDelegate.handleGatewaySelectionResponse(inAppPayment)
|
||||
}
|
||||
}
|
||||
|
||||
disposables.bindTo(viewLifecycleOwner)
|
||||
disposables += viewModel.actions.subscribe { action ->
|
||||
when (action) {
|
||||
is DonateToSignalAction.DisplayCurrencySelectionDialog -> {
|
||||
val navAction = DonateToSignalFragmentDirections.actionDonateToSignalFragmentToSetDonationCurrencyFragment(
|
||||
val navAction = DonateToSignalFragmentDirections.actionDonateToSignalFragmentToSetCurrencyFragment(
|
||||
action.inAppPaymentType,
|
||||
action.supportedCurrencies.toTypedArray()
|
||||
)
|
||||
@@ -157,23 +171,27 @@ class DonateToSignalFragment :
|
||||
}
|
||||
|
||||
is DonateToSignalAction.CancelSubscription -> {
|
||||
findNavController().safeNavigate(
|
||||
DonateToSignalFragmentDirections.actionDonateToSignalFragmentToStripePaymentInProgressFragment(
|
||||
DonationProcessorAction.CANCEL_SUBSCRIPTION,
|
||||
null,
|
||||
InAppPaymentTable.Type.RECURRING_DONATION
|
||||
)
|
||||
DonateToSignalFragmentDirections.actionDonateToSignalFragmentToStripePaymentInProgressFragment(
|
||||
DonationProcessorAction.CANCEL_SUBSCRIPTION,
|
||||
null,
|
||||
InAppPaymentType.RECURRING_DONATION
|
||||
)
|
||||
}
|
||||
|
||||
is DonateToSignalAction.UpdateSubscription -> {
|
||||
findNavController().safeNavigate(
|
||||
if (action.inAppPayment.data.paymentMethodType == InAppPaymentData.PaymentMethodType.PAYPAL) {
|
||||
DonateToSignalFragmentDirections.actionDonateToSignalFragmentToPaypalPaymentInProgressFragment(
|
||||
DonationProcessorAction.UPDATE_SUBSCRIPTION,
|
||||
action.inAppPayment,
|
||||
action.inAppPayment.type
|
||||
)
|
||||
} else {
|
||||
DonateToSignalFragmentDirections.actionDonateToSignalFragmentToStripePaymentInProgressFragment(
|
||||
DonationProcessorAction.UPDATE_SUBSCRIPTION,
|
||||
action.inAppPayment,
|
||||
action.inAppPayment.type
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -198,11 +216,6 @@ class DonateToSignalFragment :
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
donationCheckoutDelegate = null
|
||||
}
|
||||
|
||||
private fun getConfiguration(state: DonateToSignalState): DSLConfiguration {
|
||||
return configure {
|
||||
space(36.dp)
|
||||
@@ -251,14 +264,14 @@ class DonateToSignalFragment :
|
||||
space(10.dp)
|
||||
|
||||
when (state.inAppPaymentType) {
|
||||
InAppPaymentTable.Type.ONE_TIME_DONATION -> displayOneTimeSelection(state.areFieldsEnabled, state.oneTimeDonationState)
|
||||
InAppPaymentTable.Type.RECURRING_DONATION -> displayMonthlySelection(state.areFieldsEnabled, state.monthlyDonationState)
|
||||
InAppPaymentType.ONE_TIME_DONATION -> displayOneTimeSelection(state.areFieldsEnabled, state.oneTimeDonationState)
|
||||
InAppPaymentType.RECURRING_DONATION -> displayMonthlySelection(state.areFieldsEnabled, state.monthlyDonationState)
|
||||
else -> error("This fragment does not support ${state.inAppPaymentType}.")
|
||||
}
|
||||
|
||||
space(20.dp)
|
||||
|
||||
if (state.inAppPaymentType == InAppPaymentTable.Type.RECURRING_DONATION && state.monthlyDonationState.isSubscriptionActive) {
|
||||
if (state.inAppPaymentType == InAppPaymentType.RECURRING_DONATION && state.monthlyDonationState.isSubscriptionActive) {
|
||||
primaryButton(
|
||||
text = DSLSettingsText.from(R.string.SubscribeFragment__update_subscription),
|
||||
isEnabled = state.canUpdate,
|
||||
@@ -324,7 +337,7 @@ class DonateToSignalFragment :
|
||||
}
|
||||
|
||||
private fun showDonationPendingDialog(state: DonateToSignalState) {
|
||||
val message = if (state.inAppPaymentType == InAppPaymentTable.Type.ONE_TIME_DONATION) {
|
||||
val message = if (state.inAppPaymentType == InAppPaymentType.ONE_TIME_DONATION) {
|
||||
if (state.oneTimeDonationState.isOneTimeDonationLongRunning) {
|
||||
R.string.DonateToSignalFragment__bank_transfers_usually_take_1_business_day_to_process_onetime
|
||||
} else if (state.oneTimeDonationState.isNonVerifiedIdeal) {
|
||||
@@ -444,8 +457,27 @@ class DonateToSignalFragment :
|
||||
}
|
||||
}
|
||||
|
||||
private fun showSepaEuroMaximumDialog(sepaEuroMaximum: FiatMoney) {
|
||||
val max = FiatMoneyUtil.format(resources, sepaEuroMaximum, FiatMoneyUtil.formatOptions().trimZerosAfterDecimal())
|
||||
MaterialAlertDialogBuilder(requireContext())
|
||||
.setTitle(R.string.DonateToSignal__donation_amount_too_high)
|
||||
.setMessage(getString(R.string.DonateToSignalFragment__you_can_send_up_to_s_via_bank_transfer, max))
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
.show()
|
||||
}
|
||||
|
||||
override fun onBoostThanksSheetDismissed() {
|
||||
findNavController().popBackStack()
|
||||
}
|
||||
|
||||
override fun navigateToStripePaymentInProgress(inAppPayment: InAppPaymentTable.InAppPayment) {
|
||||
findNavController().safeNavigate(DonateToSignalFragmentDirections.actionDonateToSignalFragmentToStripePaymentInProgressFragment(DonationProcessorAction.PROCESS_NEW_DONATION, inAppPayment, inAppPayment.type))
|
||||
findNavController().safeNavigate(
|
||||
DonateToSignalFragmentDirections.actionDonateToSignalFragmentToStripePaymentInProgressFragment(
|
||||
DonationProcessorAction.PROCESS_NEW_DONATION,
|
||||
inAppPayment,
|
||||
inAppPayment.type
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override fun navigateToPayPalPaymentInProgress(inAppPayment: InAppPaymentTable.InAppPayment) {
|
||||
@@ -474,26 +506,19 @@ class DonateToSignalFragment :
|
||||
findNavController().safeNavigate(DonateToSignalFragmentDirections.actionDonateToSignalFragmentToThanksForYourSupportBottomSheetDialog(Badges.fromDatabaseBadge(inAppPayment.data.badge!!)))
|
||||
}
|
||||
|
||||
override fun onSubscriptionCancelled(inAppPaymentType: InAppPaymentType) {
|
||||
Snackbar.make(requireView(), R.string.SubscribeFragment__your_subscription_has_been_cancelled, Snackbar.LENGTH_LONG).show()
|
||||
}
|
||||
|
||||
override fun onProcessorActionProcessed() {
|
||||
viewModel.refreshActiveSubscription()
|
||||
// TODO [alex] - what did this used to do?
|
||||
}
|
||||
|
||||
override fun showSepaEuroMaximumDialog(sepaEuroMaximum: FiatMoney) {
|
||||
val max = FiatMoneyUtil.format(resources, sepaEuroMaximum, FiatMoneyUtil.formatOptions().trimZerosAfterDecimal())
|
||||
MaterialAlertDialogBuilder(requireContext())
|
||||
.setTitle(R.string.DonateToSignal__donation_amount_too_high)
|
||||
.setMessage(getString(R.string.DonateToSignalFragment__you_can_send_up_to_s_via_bank_transfer, max))
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
.show()
|
||||
override fun onUserLaunchedAnExternalApplication() {
|
||||
// TODO [alex] - what did this used to do?
|
||||
}
|
||||
|
||||
override fun onUserLaunchedAnExternalApplication() = Unit
|
||||
|
||||
override fun navigateToDonationPending(inAppPayment: InAppPaymentTable.InAppPayment) {
|
||||
findNavController().safeNavigate(DonateToSignalFragmentDirections.actionDonateToSignalFragmentToDonationPendingBottomSheet(inAppPayment))
|
||||
}
|
||||
|
||||
override fun onBoostThanksSheetDismissed() {
|
||||
findNavController().popBackStack()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
package org.thoughtcrime.securesms.components.settings.app.subscription.donate
|
||||
|
||||
import org.signal.core.util.money.FiatMoney
|
||||
import org.signal.donations.InAppPaymentType
|
||||
import org.thoughtcrime.securesms.badges.models.Badge
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.InAppDonations
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.boost.Boost
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.manage.NonVerifiedMonthlyDonation
|
||||
import org.thoughtcrime.securesms.database.InAppPaymentTable
|
||||
import org.thoughtcrime.securesms.database.model.InAppPaymentSubscriberRecord
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.PendingOneTimeDonation
|
||||
import org.thoughtcrime.securesms.database.model.isLongRunning
|
||||
@@ -18,71 +18,71 @@ import java.util.Currency
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
data class DonateToSignalState(
|
||||
val inAppPaymentType: InAppPaymentTable.Type,
|
||||
val inAppPaymentType: InAppPaymentType,
|
||||
val oneTimeDonationState: OneTimeDonationState = OneTimeDonationState(),
|
||||
val monthlyDonationState: MonthlyDonationState = MonthlyDonationState()
|
||||
) {
|
||||
|
||||
val areFieldsEnabled: Boolean
|
||||
get() = when (inAppPaymentType) {
|
||||
InAppPaymentTable.Type.ONE_TIME_DONATION -> oneTimeDonationState.donationStage == DonationStage.READY
|
||||
InAppPaymentTable.Type.RECURRING_DONATION -> monthlyDonationState.donationStage == DonationStage.READY
|
||||
InAppPaymentType.ONE_TIME_DONATION -> oneTimeDonationState.donationStage == DonationStage.READY
|
||||
InAppPaymentType.RECURRING_DONATION -> monthlyDonationState.donationStage == DonationStage.READY
|
||||
else -> error("This flow does not support $inAppPaymentType")
|
||||
}
|
||||
|
||||
val badge: Badge?
|
||||
get() = when (inAppPaymentType) {
|
||||
InAppPaymentTable.Type.ONE_TIME_DONATION -> oneTimeDonationState.badge
|
||||
InAppPaymentTable.Type.RECURRING_DONATION -> monthlyDonationState.selectedSubscription?.badge
|
||||
InAppPaymentType.ONE_TIME_DONATION -> oneTimeDonationState.badge
|
||||
InAppPaymentType.RECURRING_DONATION -> monthlyDonationState.selectedSubscription?.badge
|
||||
else -> error("This flow does not support $inAppPaymentType")
|
||||
}
|
||||
|
||||
val canSetCurrency: Boolean
|
||||
get() = when (inAppPaymentType) {
|
||||
InAppPaymentTable.Type.ONE_TIME_DONATION -> areFieldsEnabled && !oneTimeDonationState.isOneTimeDonationPending
|
||||
InAppPaymentTable.Type.RECURRING_DONATION -> areFieldsEnabled && !monthlyDonationState.isSubscriptionActive
|
||||
InAppPaymentType.ONE_TIME_DONATION -> areFieldsEnabled && !oneTimeDonationState.isOneTimeDonationPending
|
||||
InAppPaymentType.RECURRING_DONATION -> areFieldsEnabled && !monthlyDonationState.isSubscriptionActive
|
||||
else -> error("This flow does not support $inAppPaymentType")
|
||||
}
|
||||
|
||||
val selectedCurrency: Currency
|
||||
get() = when (inAppPaymentType) {
|
||||
InAppPaymentTable.Type.ONE_TIME_DONATION -> oneTimeDonationState.selectedCurrency
|
||||
InAppPaymentTable.Type.RECURRING_DONATION -> monthlyDonationState.selectedCurrency
|
||||
InAppPaymentType.ONE_TIME_DONATION -> oneTimeDonationState.selectedCurrency
|
||||
InAppPaymentType.RECURRING_DONATION -> monthlyDonationState.selectedCurrency
|
||||
else -> error("This flow does not support $inAppPaymentType")
|
||||
}
|
||||
|
||||
val selectableCurrencyCodes: List<String>
|
||||
get() = when (inAppPaymentType) {
|
||||
InAppPaymentTable.Type.ONE_TIME_DONATION -> oneTimeDonationState.selectableCurrencyCodes
|
||||
InAppPaymentTable.Type.RECURRING_DONATION -> monthlyDonationState.selectableCurrencyCodes
|
||||
InAppPaymentType.ONE_TIME_DONATION -> oneTimeDonationState.selectableCurrencyCodes
|
||||
InAppPaymentType.RECURRING_DONATION -> monthlyDonationState.selectableCurrencyCodes
|
||||
else -> error("This flow does not support $inAppPaymentType")
|
||||
}
|
||||
|
||||
val level: Int
|
||||
get() = when (inAppPaymentType) {
|
||||
InAppPaymentTable.Type.ONE_TIME_DONATION -> 1
|
||||
InAppPaymentTable.Type.RECURRING_DONATION -> monthlyDonationState.selectedSubscription!!.level
|
||||
InAppPaymentType.ONE_TIME_DONATION -> 1
|
||||
InAppPaymentType.RECURRING_DONATION -> monthlyDonationState.selectedSubscription!!.level
|
||||
else -> error("This flow does not support $inAppPaymentType")
|
||||
}
|
||||
|
||||
val continueEnabled: Boolean
|
||||
get() = when (inAppPaymentType) {
|
||||
InAppPaymentTable.Type.ONE_TIME_DONATION -> areFieldsEnabled && oneTimeDonationState.isSelectionValid && InAppDonations.hasAtLeastOnePaymentMethodAvailable()
|
||||
InAppPaymentTable.Type.RECURRING_DONATION -> areFieldsEnabled && monthlyDonationState.isSelectionValid && InAppDonations.hasAtLeastOnePaymentMethodAvailable()
|
||||
InAppPaymentType.ONE_TIME_DONATION -> areFieldsEnabled && oneTimeDonationState.isSelectionValid && InAppDonations.hasAtLeastOnePaymentMethodAvailable()
|
||||
InAppPaymentType.RECURRING_DONATION -> areFieldsEnabled && monthlyDonationState.isSelectionValid && InAppDonations.hasAtLeastOnePaymentMethodAvailable()
|
||||
else -> error("This flow does not support $inAppPaymentType")
|
||||
}
|
||||
|
||||
val canContinue: Boolean
|
||||
get() = when (inAppPaymentType) {
|
||||
InAppPaymentTable.Type.ONE_TIME_DONATION -> continueEnabled && !oneTimeDonationState.isOneTimeDonationPending
|
||||
InAppPaymentTable.Type.RECURRING_DONATION -> continueEnabled && !monthlyDonationState.isSubscriptionActive && !monthlyDonationState.transactionState.isInProgress
|
||||
InAppPaymentType.ONE_TIME_DONATION -> continueEnabled && !oneTimeDonationState.isOneTimeDonationPending
|
||||
InAppPaymentType.RECURRING_DONATION -> continueEnabled && !monthlyDonationState.isSubscriptionActive && !monthlyDonationState.transactionState.isInProgress
|
||||
else -> error("This flow does not support $inAppPaymentType")
|
||||
}
|
||||
|
||||
val canUpdate: Boolean
|
||||
get() = when (inAppPaymentType) {
|
||||
InAppPaymentTable.Type.ONE_TIME_DONATION -> false
|
||||
InAppPaymentTable.Type.RECURRING_DONATION -> areFieldsEnabled && monthlyDonationState.isSelectionValid
|
||||
InAppPaymentType.ONE_TIME_DONATION -> false
|
||||
InAppPaymentType.RECURRING_DONATION -> areFieldsEnabled && monthlyDonationState.isSelectionValid
|
||||
else -> error("This flow does not support $inAppPaymentType")
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ import org.signal.core.util.logging.Log
|
||||
import org.signal.core.util.money.FiatMoney
|
||||
import org.signal.core.util.money.PlatformCurrencyUtil
|
||||
import org.signal.core.util.orNull
|
||||
import org.signal.donations.InAppPaymentType
|
||||
import org.thoughtcrime.securesms.badges.Badges
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.DonationSerializationHelper.toFiatValue
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.InAppPaymentsRepository
|
||||
@@ -50,7 +51,7 @@ import java.util.Optional
|
||||
* only in charge of rendering our "current view of the world."
|
||||
*/
|
||||
class DonateToSignalViewModel(
|
||||
startType: InAppPaymentTable.Type,
|
||||
startType: InAppPaymentType,
|
||||
private val subscriptionsRepository: RecurringInAppPaymentRepository,
|
||||
private val oneTimeInAppPaymentRepository: OneTimeInAppPaymentRepository
|
||||
) : ViewModel() {
|
||||
@@ -137,8 +138,8 @@ class DonateToSignalViewModel(
|
||||
store.update {
|
||||
it.copy(
|
||||
inAppPaymentType = when (it.inAppPaymentType) {
|
||||
InAppPaymentTable.Type.ONE_TIME_DONATION -> InAppPaymentTable.Type.RECURRING_DONATION
|
||||
InAppPaymentTable.Type.RECURRING_DONATION -> InAppPaymentTable.Type.ONE_TIME_DONATION
|
||||
InAppPaymentType.ONE_TIME_DONATION -> InAppPaymentType.RECURRING_DONATION
|
||||
InAppPaymentType.RECURRING_DONATION -> InAppPaymentType.ONE_TIME_DONATION
|
||||
else -> error("Should never get here.")
|
||||
}
|
||||
)
|
||||
@@ -222,8 +223,8 @@ class DonateToSignalViewModel(
|
||||
|
||||
private fun getAmount(snapshot: DonateToSignalState): FiatMoney {
|
||||
return when (snapshot.inAppPaymentType) {
|
||||
InAppPaymentTable.Type.ONE_TIME_DONATION -> getOneTimeAmount(snapshot.oneTimeDonationState)
|
||||
InAppPaymentTable.Type.RECURRING_DONATION -> getSelectedSubscriptionCost()
|
||||
InAppPaymentType.ONE_TIME_DONATION -> getOneTimeAmount(snapshot.oneTimeDonationState)
|
||||
InAppPaymentType.RECURRING_DONATION -> getSelectedSubscriptionCost()
|
||||
else -> error("This ViewModel does not support ${snapshot.inAppPaymentType}.")
|
||||
}
|
||||
}
|
||||
@@ -237,7 +238,7 @@ class DonateToSignalViewModel(
|
||||
}
|
||||
|
||||
private fun initializeOneTimeDonationState(oneTimeInAppPaymentRepository: OneTimeInAppPaymentRepository) {
|
||||
val oneTimeDonationFromJob: Observable<Optional<PendingOneTimeDonation>> = InAppPaymentsRepository.observeInAppPaymentRedemption(InAppPaymentTable.Type.ONE_TIME_DONATION).map {
|
||||
val oneTimeDonationFromJob: Observable<Optional<PendingOneTimeDonation>> = InAppPaymentsRepository.observeInAppPaymentRedemption(InAppPaymentType.ONE_TIME_DONATION).map {
|
||||
when (it) {
|
||||
is DonationRedemptionJobStatus.PendingExternalVerification -> Optional.ofNullable(it.pendingOneTimeDonation)
|
||||
|
||||
@@ -331,7 +332,7 @@ class DonateToSignalViewModel(
|
||||
}
|
||||
|
||||
private fun monitorLevelUpdateProcessing() {
|
||||
val redemptionJobStatus: Observable<DonationRedemptionJobStatus> = InAppPaymentsRepository.observeInAppPaymentRedemption(InAppPaymentTable.Type.RECURRING_DONATION)
|
||||
val redemptionJobStatus: Observable<DonationRedemptionJobStatus> = InAppPaymentsRepository.observeInAppPaymentRedemption(InAppPaymentType.RECURRING_DONATION)
|
||||
|
||||
monthlyDonationDisposables += Observable
|
||||
.combineLatest(redemptionJobStatus, LevelUpdate.isProcessing, ::Pair)
|
||||
@@ -420,7 +421,7 @@ class DonateToSignalViewModel(
|
||||
}
|
||||
|
||||
class Factory(
|
||||
private val startType: InAppPaymentTable.Type,
|
||||
private val startType: InAppPaymentType,
|
||||
private val subscriptionsRepository: RecurringInAppPaymentRepository = RecurringInAppPaymentRepository(AppDependencies.donationsService),
|
||||
private val oneTimeInAppPaymentRepository: OneTimeInAppPaymentRepository = OneTimeInAppPaymentRepository(AppDependencies.donationsService)
|
||||
) : ViewModelProvider.Factory {
|
||||
|
||||
@@ -11,7 +11,6 @@ import androidx.navigation.fragment.findNavController
|
||||
import androidx.navigation.navGraphViewModels
|
||||
import com.google.android.gms.wallet.PaymentData
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.rxjava3.core.Flowable
|
||||
import io.reactivex.rxjava3.core.Single
|
||||
@@ -20,16 +19,15 @@ import io.reactivex.rxjava3.schedulers.Schedulers
|
||||
import org.signal.core.util.concurrent.LifecycleDisposable
|
||||
import org.signal.core.util.getParcelableCompat
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.signal.core.util.money.FiatMoney
|
||||
import org.signal.donations.GooglePayApi
|
||||
import org.signal.donations.InAppPaymentType
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.DonationPaymentComponent
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.DonationSerializationHelper.toFiatMoney
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.InAppDonations
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.InAppPaymentComponent
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.InAppPaymentsRepository
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.InAppPaymentsRepository.toPaymentSourceType
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.donate.card.CreditCardFragment
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.donate.gateway.GatewaySelectorBottomSheet
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.donate.paypal.PayPalPaymentInProgressFragment
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.donate.stripe.StripePaymentInProgressFragment
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.donate.stripe.StripePaymentInProgressViewModel
|
||||
@@ -40,9 +38,7 @@ import org.thoughtcrime.securesms.components.settings.app.subscription.errors.Do
|
||||
import org.thoughtcrime.securesms.database.InAppPaymentTable
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.InAppPaymentData
|
||||
import org.thoughtcrime.securesms.payments.currency.CurrencyUtil
|
||||
import org.thoughtcrime.securesms.util.fragments.requireListener
|
||||
import java.math.BigDecimal
|
||||
|
||||
/**
|
||||
* Abstracts out some common UI-level interactions between gift flow and normal donate flow.
|
||||
@@ -57,15 +53,14 @@ class DonationCheckoutDelegate(
|
||||
private val TAG = Log.tag(DonationCheckoutDelegate::class.java)
|
||||
}
|
||||
|
||||
private lateinit var donationPaymentComponent: DonationPaymentComponent
|
||||
private val inAppPaymentComponent: InAppPaymentComponent by lazy { fragment.requireListener() }
|
||||
private val disposables = LifecycleDisposable()
|
||||
private val viewModel: DonationCheckoutViewModel by fragment.viewModels()
|
||||
|
||||
private val stripePaymentViewModel: StripePaymentInProgressViewModel by fragment.navGraphViewModels(
|
||||
R.id.donate_to_signal,
|
||||
R.id.checkout_flow,
|
||||
factoryProducer = {
|
||||
donationPaymentComponent = fragment.requireListener()
|
||||
StripePaymentInProgressViewModel.Factory(donationPaymentComponent.stripeRepository)
|
||||
StripePaymentInProgressViewModel.Factory(inAppPaymentComponent.stripeRepository)
|
||||
}
|
||||
)
|
||||
|
||||
@@ -76,18 +71,8 @@ class DonationCheckoutDelegate(
|
||||
|
||||
override fun onCreate(owner: LifecycleOwner) {
|
||||
disposables.bindTo(fragment.viewLifecycleOwner)
|
||||
donationPaymentComponent = fragment.requireListener()
|
||||
registerGooglePayCallback()
|
||||
|
||||
fragment.setFragmentResultListener(GatewaySelectorBottomSheet.REQUEST_KEY) { _, bundle ->
|
||||
if (bundle.containsKey(GatewaySelectorBottomSheet.FAILURE_KEY)) {
|
||||
callback.showSepaEuroMaximumDialog(FiatMoney(bundle.getSerializable(GatewaySelectorBottomSheet.SEPA_EURO_MAX) as BigDecimal, CurrencyUtil.EURO))
|
||||
} else {
|
||||
val inAppPayment: InAppPaymentTable.InAppPayment = bundle.getParcelableCompat(GatewaySelectorBottomSheet.REQUEST_KEY, InAppPaymentTable.InAppPayment::class.java)!!
|
||||
handleGatewaySelectionResponse(inAppPayment)
|
||||
}
|
||||
}
|
||||
|
||||
fragment.setFragmentResultListener(StripePaymentInProgressFragment.REQUEST_KEY) { _, bundle ->
|
||||
val result: DonationProcessorActionResult = bundle.getParcelableCompat(StripePaymentInProgressFragment.REQUEST_KEY, DonationProcessorActionResult::class.java)!!
|
||||
handleDonationProcessorActionResult(result)
|
||||
@@ -114,7 +99,7 @@ class DonationCheckoutDelegate(
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleGatewaySelectionResponse(inAppPayment: InAppPaymentTable.InAppPayment) {
|
||||
fun handleGatewaySelectionResponse(inAppPayment: InAppPaymentTable.InAppPayment) {
|
||||
if (InAppDonations.isPaymentSourceAvailable(inAppPayment.data.paymentMethodType.toPaymentSourceType(), inAppPayment.type)) {
|
||||
when (inAppPayment.data.paymentMethodType) {
|
||||
InAppPaymentData.PaymentMethodType.GOOGLE_PAY -> launchGooglePay(inAppPayment)
|
||||
@@ -140,7 +125,7 @@ class DonationCheckoutDelegate(
|
||||
|
||||
private fun handleSuccessfulDonationProcessorActionResult(result: DonationProcessorActionResult) {
|
||||
if (result.action == DonationProcessorAction.CANCEL_SUBSCRIPTION) {
|
||||
Snackbar.make(fragment.requireView(), R.string.SubscribeFragment__your_subscription_has_been_cancelled, Snackbar.LENGTH_LONG).show()
|
||||
callback.onSubscriptionCancelled(result.inAppPaymentType)
|
||||
} else {
|
||||
callback.onPaymentComplete(result.inAppPayment!!)
|
||||
}
|
||||
@@ -152,7 +137,7 @@ class DonationCheckoutDelegate(
|
||||
.setTitle(R.string.DonationsErrors__failed_to_cancel_subscription)
|
||||
.setMessage(R.string.DonationsErrors__subscription_cancellation_requires_an_internet_connection)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
fragment.findNavController().popBackStack()
|
||||
fragment.findNavController().popBackStack(R.id.checkout_flow, true)
|
||||
}
|
||||
.show()
|
||||
} else {
|
||||
@@ -166,7 +151,7 @@ class DonationCheckoutDelegate(
|
||||
|
||||
private fun launchGooglePay(inAppPayment: InAppPaymentTable.InAppPayment) {
|
||||
viewModel.provideGatewayRequestForGooglePay(inAppPayment)
|
||||
donationPaymentComponent.stripeRepository.requestTokenFromGooglePay(
|
||||
inAppPaymentComponent.stripeRepository.requestTokenFromGooglePay(
|
||||
price = inAppPayment.data.amount!!.toFiatMoney(),
|
||||
label = inAppPayment.data.label,
|
||||
requestCode = InAppPaymentsRepository.getGooglePayRequestCode(inAppPayment.type)
|
||||
@@ -186,10 +171,10 @@ class DonationCheckoutDelegate(
|
||||
}
|
||||
|
||||
private fun registerGooglePayCallback() {
|
||||
disposables += donationPaymentComponent.googlePayResultPublisher.subscribeBy(
|
||||
disposables += inAppPaymentComponent.googlePayResultPublisher.subscribeBy(
|
||||
onNext = { paymentResult ->
|
||||
viewModel.consumeGatewayRequestForGooglePay()?.let {
|
||||
donationPaymentComponent.stripeRepository.onActivityResult(
|
||||
inAppPaymentComponent.stripeRepository.onActivityResult(
|
||||
paymentResult.requestCode,
|
||||
paymentResult.resultCode,
|
||||
paymentResult.data,
|
||||
@@ -366,7 +351,7 @@ class DonationCheckoutDelegate(
|
||||
errorDialog = null
|
||||
if (!tryAgain) {
|
||||
tryAgain = false
|
||||
fragment?.findNavController()?.popBackStack()
|
||||
fragment?.findNavController()?.popBackStack(R.id.checkout_flow, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -384,7 +369,7 @@ class DonationCheckoutDelegate(
|
||||
fun navigateToIdealDetailsFragment(inAppPayment: InAppPaymentTable.InAppPayment)
|
||||
fun navigateToBankTransferMandate(inAppPayment: InAppPaymentTable.InAppPayment)
|
||||
fun onPaymentComplete(inAppPayment: InAppPaymentTable.InAppPayment)
|
||||
fun onSubscriptionCancelled(inAppPaymentType: InAppPaymentType)
|
||||
fun onProcessorActionProcessed()
|
||||
fun showSepaEuroMaximumDialog(sepaEuroMaximum: FiatMoney)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package org.thoughtcrime.securesms.components.settings.app.subscription.donate
|
||||
|
||||
import com.google.android.material.button.MaterialButton
|
||||
import org.signal.donations.InAppPaymentType
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.database.InAppPaymentTable
|
||||
import org.thoughtcrime.securesms.databinding.DonationPillToggleBinding
|
||||
import org.thoughtcrime.securesms.util.adapter.mapping.BindingFactory
|
||||
import org.thoughtcrime.securesms.util.adapter.mapping.BindingViewHolder
|
||||
@@ -16,7 +16,7 @@ object DonationPillToggle {
|
||||
}
|
||||
|
||||
class Model(
|
||||
val selected: InAppPaymentTable.Type,
|
||||
val selected: InAppPaymentType,
|
||||
val onClick: () -> Unit
|
||||
) : MappingModel<Model> {
|
||||
override fun areItemsTheSame(newItem: Model): Boolean = true
|
||||
@@ -29,10 +29,10 @@ object DonationPillToggle {
|
||||
private class ViewHolder(binding: DonationPillToggleBinding) : BindingViewHolder<Model, DonationPillToggleBinding>(binding) {
|
||||
override fun bind(model: Model) {
|
||||
when (model.selected) {
|
||||
InAppPaymentTable.Type.ONE_TIME_DONATION -> {
|
||||
InAppPaymentType.ONE_TIME_DONATION -> {
|
||||
presentButtons(model, binding.oneTime, binding.monthly)
|
||||
}
|
||||
InAppPaymentTable.Type.RECURRING_DONATION -> {
|
||||
InAppPaymentType.RECURRING_DONATION -> {
|
||||
presentButtons(model, binding.monthly, binding.oneTime)
|
||||
}
|
||||
else -> {
|
||||
|
||||
@@ -2,12 +2,14 @@ package org.thoughtcrime.securesms.components.settings.app.subscription.donate
|
||||
|
||||
import android.os.Parcelable
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import org.signal.donations.InAppPaymentType
|
||||
import org.thoughtcrime.securesms.database.InAppPaymentTable
|
||||
|
||||
@Parcelize
|
||||
class DonationProcessorActionResult(
|
||||
val action: DonationProcessorAction,
|
||||
val inAppPayment: InAppPaymentTable.InAppPayment?,
|
||||
val inAppPaymentType: InAppPaymentType,
|
||||
val status: Status
|
||||
) : Parcelable {
|
||||
enum class Status {
|
||||
|
||||
@@ -16,17 +16,17 @@ import androidx.navigation.fragment.navArgs
|
||||
import androidx.navigation.navGraphViewModels
|
||||
import org.signal.core.util.concurrent.LifecycleDisposable
|
||||
import org.signal.core.util.getParcelableCompat
|
||||
import org.signal.donations.InAppPaymentType
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.components.TemporaryScreenshotSecurity
|
||||
import org.thoughtcrime.securesms.components.ViewBinderDelegate
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.DonationPaymentComponent
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.DonationSerializationHelper.toFiatMoney
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.InAppPaymentComponent
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.donate.DonationCheckoutDelegate
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.donate.DonationProcessorAction
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.donate.DonationProcessorActionResult
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.donate.stripe.StripePaymentInProgressFragment
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.donate.stripe.StripePaymentInProgressViewModel
|
||||
import org.thoughtcrime.securesms.database.InAppPaymentTable
|
||||
import org.thoughtcrime.securesms.databinding.CreditCardFragmentBinding
|
||||
import org.thoughtcrime.securesms.payments.FiatMoneyUtil
|
||||
import org.thoughtcrime.securesms.util.ViewUtil
|
||||
@@ -40,9 +40,9 @@ class CreditCardFragment : Fragment(R.layout.credit_card_fragment) {
|
||||
private val viewModel: CreditCardViewModel by viewModels()
|
||||
private val lifecycleDisposable = LifecycleDisposable()
|
||||
private val stripePaymentViewModel: StripePaymentInProgressViewModel by navGraphViewModels(
|
||||
R.id.donate_to_signal,
|
||||
R.id.checkout_flow,
|
||||
factoryProducer = {
|
||||
StripePaymentInProgressViewModel.Factory(requireListener<DonationPaymentComponent>().stripeRepository)
|
||||
StripePaymentInProgressViewModel.Factory(requireListener<InAppPaymentComponent>().stripeRepository)
|
||||
}
|
||||
)
|
||||
|
||||
@@ -59,7 +59,7 @@ class CreditCardFragment : Fragment(R.layout.credit_card_fragment) {
|
||||
}
|
||||
|
||||
// TODO [message-backups] Copy for this button in backups checkout flow.
|
||||
binding.continueButton.text = if (args.inAppPayment.type == InAppPaymentTable.Type.RECURRING_DONATION) {
|
||||
binding.continueButton.text = if (args.inAppPayment.type == InAppPaymentType.RECURRING_DONATION) {
|
||||
getString(
|
||||
R.string.CreditCardFragment__donate_s_month,
|
||||
FiatMoneyUtil.format(resources, args.inAppPayment.data.amount!!.toFiatMoney(), FiatMoneyUtil.formatOptions().trimZerosAfterDecimal())
|
||||
|
||||
@@ -10,6 +10,7 @@ import androidx.navigation.fragment.navArgs
|
||||
import io.reactivex.rxjava3.kotlin.subscribeBy
|
||||
import org.signal.core.util.concurrent.LifecycleDisposable
|
||||
import org.signal.core.util.dp
|
||||
import org.signal.donations.InAppPaymentType
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.badges.Badges
|
||||
import org.thoughtcrime.securesms.badges.models.BadgeDisplay112
|
||||
@@ -19,8 +20,8 @@ import org.thoughtcrime.securesms.components.settings.DSLSettingsBottomSheetFrag
|
||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsIcon
|
||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsText
|
||||
import org.thoughtcrime.securesms.components.settings.NO_TINT
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.DonationPaymentComponent
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.DonationSerializationHelper.toFiatMoney
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.InAppPaymentComponent
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.models.GooglePayButton
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.models.PayPalButton
|
||||
import org.thoughtcrime.securesms.components.settings.configure
|
||||
@@ -41,7 +42,7 @@ class GatewaySelectorBottomSheet : DSLSettingsBottomSheetFragment() {
|
||||
private val args: GatewaySelectorBottomSheetArgs by navArgs()
|
||||
|
||||
private val viewModel: GatewaySelectorViewModel by viewModels(factoryProducer = {
|
||||
GatewaySelectorViewModel.Factory(args, requireListener<DonationPaymentComponent>().stripeRepository)
|
||||
GatewaySelectorViewModel.Factory(args, requireListener<InAppPaymentComponent>().stripeRepository)
|
||||
})
|
||||
|
||||
override fun bindAdapter(adapter: DSLSettingsAdapter) {
|
||||
@@ -206,11 +207,11 @@ class GatewaySelectorBottomSheet : DSLSettingsBottomSheetFragment() {
|
||||
|
||||
fun DSLConfiguration.presentTitleAndSubtitle(context: Context, inAppPayment: InAppPaymentTable.InAppPayment) {
|
||||
when (inAppPayment.type) {
|
||||
InAppPaymentTable.Type.UNKNOWN -> error("Unsupported type UNKNOWN")
|
||||
InAppPaymentTable.Type.RECURRING_BACKUP -> error("This type is not supported") // TODO [message-backups] necessary?
|
||||
InAppPaymentTable.Type.RECURRING_DONATION -> presentMonthlyText(context, inAppPayment)
|
||||
InAppPaymentTable.Type.ONE_TIME_DONATION -> presentOneTimeText(context, inAppPayment)
|
||||
InAppPaymentTable.Type.ONE_TIME_GIFT -> presentGiftText(context, inAppPayment)
|
||||
InAppPaymentType.UNKNOWN -> error("Unsupported type UNKNOWN")
|
||||
InAppPaymentType.RECURRING_BACKUP -> error("This type is not supported") // TODO [message-backups] necessary?
|
||||
InAppPaymentType.RECURRING_DONATION -> presentMonthlyText(context, inAppPayment)
|
||||
InAppPaymentType.ONE_TIME_DONATION -> presentOneTimeText(context, inAppPayment)
|
||||
InAppPaymentType.ONE_TIME_GIFT -> presentGiftText(context, inAppPayment)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ import org.signal.core.util.getParcelableCompat
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.components.ViewBinderDelegate
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.InAppPaymentsRepository.toErrorSource
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.donate.DonationProcessorAction
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.donate.DonationProcessorActionResult
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.donate.DonationProcessorStage
|
||||
@@ -44,7 +45,7 @@ class PayPalPaymentInProgressFragment : DialogFragment(R.layout.donation_in_prog
|
||||
private val binding by ViewBinderDelegate(DonationInProgressFragmentBinding::bind)
|
||||
private val args: PayPalPaymentInProgressFragmentArgs by navArgs()
|
||||
|
||||
private val viewModel: PayPalPaymentInProgressViewModel by navGraphViewModels(R.id.donate_to_signal, factoryProducer = {
|
||||
private val viewModel: PayPalPaymentInProgressViewModel by navGraphViewModels(R.id.checkout_flow, factoryProducer = {
|
||||
PayPalPaymentInProgressViewModel.Factory()
|
||||
})
|
||||
|
||||
@@ -62,9 +63,11 @@ class PayPalPaymentInProgressFragment : DialogFragment(R.layout.donation_in_prog
|
||||
DonationProcessorAction.PROCESS_NEW_DONATION -> {
|
||||
viewModel.processNewDonation(args.inAppPayment!!, this::oneTimeConfirmationPipeline, this::monthlyConfirmationPipeline)
|
||||
}
|
||||
|
||||
DonationProcessorAction.UPDATE_SUBSCRIPTION -> {
|
||||
viewModel.updateSubscription(args.inAppPayment!!)
|
||||
}
|
||||
|
||||
DonationProcessorAction.CANCEL_SUBSCRIPTION -> {
|
||||
viewModel.cancelSubscription(InAppPaymentSubscriberRecord.Type.DONATION) // TODO [message-backups] Remove hardcode
|
||||
}
|
||||
@@ -90,11 +93,13 @@ class PayPalPaymentInProgressFragment : DialogFragment(R.layout.donation_in_prog
|
||||
REQUEST_KEY to DonationProcessorActionResult(
|
||||
action = args.action,
|
||||
inAppPayment = args.inAppPayment,
|
||||
inAppPaymentType = args.inAppPaymentType,
|
||||
status = DonationProcessorActionResult.Status.FAILURE
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
DonationProcessorStage.COMPLETE -> {
|
||||
viewModel.onEndAction()
|
||||
findNavController().popBackStack()
|
||||
@@ -104,11 +109,13 @@ class PayPalPaymentInProgressFragment : DialogFragment(R.layout.donation_in_prog
|
||||
REQUEST_KEY to DonationProcessorActionResult(
|
||||
action = args.action,
|
||||
inAppPayment = args.inAppPayment,
|
||||
inAppPaymentType = args.inAppPaymentType,
|
||||
status = DonationProcessorActionResult.Status.SUCCESS
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
DonationProcessorStage.CANCELLING -> binding.progressCardStatus.setText(R.string.StripePaymentInProgressFragment__cancelling)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,9 +11,11 @@ import io.reactivex.rxjava3.kotlin.plusAssign
|
||||
import io.reactivex.rxjava3.kotlin.subscribeBy
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.signal.donations.InAppPaymentType
|
||||
import org.signal.donations.PaymentSourceType
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.DonationSerializationHelper.toFiatMoney
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.InAppPaymentsRepository
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.InAppPaymentsRepository.requireSubscriberType
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.OneTimeInAppPaymentRepository
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.PayPalRepository
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.RecurringInAppPaymentRepository
|
||||
@@ -123,7 +125,7 @@ class PayPalPaymentInProgressViewModel(
|
||||
) {
|
||||
Log.d(TAG, "Proceeding with one-time payment pipeline...", true)
|
||||
store.update { DonationProcessorStage.PAYMENT_PIPELINE }
|
||||
val verifyUser = if (inAppPayment.type == InAppPaymentTable.Type.ONE_TIME_GIFT) {
|
||||
val verifyUser = if (inAppPayment.type == InAppPaymentType.ONE_TIME_GIFT) {
|
||||
OneTimeInAppPaymentRepository.verifyRecipientIsAllowedToReceiveAGift(RecipientId.from(inAppPayment.data.recipientId!!))
|
||||
} else {
|
||||
Completable.complete()
|
||||
@@ -168,7 +170,7 @@ class PayPalPaymentInProgressViewModel(
|
||||
}
|
||||
|
||||
private fun proceedMonthly(inAppPayment: InAppPaymentTable.InAppPayment, routeToPaypalConfirmation: (PayPalCreatePaymentMethodResponse) -> Single<PayPalPaymentMethodId>) {
|
||||
Log.d(TAG, "Proceeding with monthly payment pipeline...")
|
||||
Log.d(TAG, "Proceeding with monthly payment pipeline for InAppPayment::${inAppPayment.id} of type ${inAppPayment.type}...", true)
|
||||
|
||||
val setup = recurringInAppPaymentRepository.ensureSubscriberId(inAppPayment.type.requireSubscriberType())
|
||||
.andThen(recurringInAppPaymentRepository.cancelActiveSubscriptionIfNecessary(inAppPayment.type.requireSubscriberType()))
|
||||
|
||||
@@ -7,6 +7,7 @@ package org.thoughtcrime.securesms.components.settings.app.subscription.donate.s
|
||||
import android.os.Parcelable
|
||||
import kotlinx.parcelize.IgnoredOnParcel
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import org.signal.donations.InAppPaymentType
|
||||
import org.signal.donations.PaymentSourceType
|
||||
import org.signal.donations.StripeIntentAccessor
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.InAppPaymentsRepository.toPaymentMethodType
|
||||
@@ -43,12 +44,12 @@ data class Stripe3DSData(
|
||||
intentClientSecret = stripeIntentAccessor.intentClientSecret
|
||||
),
|
||||
gatewayRequest = ExternalLaunchTransactionState.GatewayRequest(
|
||||
donateToSignalType = when (inAppPayment.type) {
|
||||
InAppPaymentTable.Type.UNKNOWN -> error("Unsupported type UNKNOWN")
|
||||
InAppPaymentTable.Type.ONE_TIME_DONATION -> ExternalLaunchTransactionState.GatewayRequest.DonateToSignalType.ONE_TIME
|
||||
InAppPaymentTable.Type.RECURRING_DONATION -> ExternalLaunchTransactionState.GatewayRequest.DonateToSignalType.MONTHLY
|
||||
InAppPaymentTable.Type.ONE_TIME_GIFT -> ExternalLaunchTransactionState.GatewayRequest.DonateToSignalType.GIFT
|
||||
InAppPaymentTable.Type.RECURRING_BACKUP -> error("Unimplemented") // TODO [message-backups] do we still need this?
|
||||
inAppPaymentType = when (inAppPayment.type) {
|
||||
InAppPaymentType.UNKNOWN -> error("Unsupported type UNKNOWN")
|
||||
InAppPaymentType.ONE_TIME_DONATION -> ExternalLaunchTransactionState.GatewayRequest.InAppPaymentType.ONE_TIME_DONATION
|
||||
InAppPaymentType.RECURRING_DONATION -> ExternalLaunchTransactionState.GatewayRequest.InAppPaymentType.RECURRING_DONATION
|
||||
InAppPaymentType.ONE_TIME_GIFT -> ExternalLaunchTransactionState.GatewayRequest.InAppPaymentType.ONE_TIME_GIFT
|
||||
InAppPaymentType.RECURRING_BACKUP -> ExternalLaunchTransactionState.GatewayRequest.InAppPaymentType.RECURRING_BACKUPS
|
||||
},
|
||||
badge = inAppPayment.data.badge,
|
||||
label = inAppPayment.data.label,
|
||||
@@ -76,11 +77,11 @@ data class Stripe3DSData(
|
||||
),
|
||||
inAppPayment = InAppPaymentTable.InAppPayment(
|
||||
id = InAppPaymentTable.InAppPaymentId(-1), // TODO [alex] -- can we start writing this in for new transactions?
|
||||
type = when (proto.gatewayRequest!!.donateToSignalType) {
|
||||
ExternalLaunchTransactionState.GatewayRequest.DonateToSignalType.MONTHLY -> InAppPaymentTable.Type.RECURRING_DONATION
|
||||
ExternalLaunchTransactionState.GatewayRequest.DonateToSignalType.ONE_TIME -> InAppPaymentTable.Type.ONE_TIME_DONATION
|
||||
ExternalLaunchTransactionState.GatewayRequest.DonateToSignalType.GIFT -> InAppPaymentTable.Type.ONE_TIME_GIFT
|
||||
// TODO [message-backups] -- Backups?
|
||||
type = when (proto.gatewayRequest!!.inAppPaymentType) {
|
||||
ExternalLaunchTransactionState.GatewayRequest.InAppPaymentType.RECURRING_DONATION -> InAppPaymentType.RECURRING_DONATION
|
||||
ExternalLaunchTransactionState.GatewayRequest.InAppPaymentType.ONE_TIME_DONATION -> InAppPaymentType.ONE_TIME_DONATION
|
||||
ExternalLaunchTransactionState.GatewayRequest.InAppPaymentType.ONE_TIME_GIFT -> InAppPaymentType.ONE_TIME_GIFT
|
||||
ExternalLaunchTransactionState.GatewayRequest.InAppPaymentType.RECURRING_BACKUPS -> InAppPaymentType.RECURRING_BACKUP
|
||||
},
|
||||
endOfPeriod = 0.milliseconds,
|
||||
updatedAt = 0.milliseconds,
|
||||
|
||||
@@ -23,7 +23,9 @@ import org.signal.donations.StripeApi
|
||||
import org.signal.donations.StripeIntentAccessor
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.components.ViewBinderDelegate
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.DonationPaymentComponent
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.InAppPaymentComponent
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.InAppPaymentsRepository.requireSubscriberType
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.InAppPaymentsRepository.toErrorSource
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.donate.DonationProcessorAction
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.donate.DonationProcessorActionResult
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.donate.DonationProcessorStage
|
||||
@@ -46,9 +48,9 @@ class StripePaymentInProgressFragment : DialogFragment(R.layout.donation_in_prog
|
||||
private val disposables = LifecycleDisposable()
|
||||
|
||||
private val viewModel: StripePaymentInProgressViewModel by navGraphViewModels(
|
||||
R.id.donate_to_signal,
|
||||
R.id.checkout_flow,
|
||||
factoryProducer = {
|
||||
StripePaymentInProgressViewModel.Factory(requireListener<DonationPaymentComponent>().stripeRepository)
|
||||
StripePaymentInProgressViewModel.Factory(requireListener<InAppPaymentComponent>().stripeRepository)
|
||||
}
|
||||
)
|
||||
|
||||
@@ -94,6 +96,7 @@ class StripePaymentInProgressFragment : DialogFragment(R.layout.donation_in_prog
|
||||
REQUEST_KEY to DonationProcessorActionResult(
|
||||
action = args.action,
|
||||
inAppPayment = args.inAppPayment,
|
||||
inAppPaymentType = args.inAppPaymentType,
|
||||
status = DonationProcessorActionResult.Status.FAILURE
|
||||
)
|
||||
)
|
||||
@@ -108,6 +111,7 @@ class StripePaymentInProgressFragment : DialogFragment(R.layout.donation_in_prog
|
||||
REQUEST_KEY to DonationProcessorActionResult(
|
||||
action = args.action,
|
||||
inAppPayment = args.inAppPayment,
|
||||
inAppPaymentType = args.inAppPaymentType,
|
||||
status = DonationProcessorActionResult.Status.SUCCESS
|
||||
)
|
||||
)
|
||||
|
||||
@@ -13,11 +13,14 @@ import io.reactivex.rxjava3.kotlin.subscribeBy
|
||||
import org.signal.core.util.concurrent.SignalExecutors
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.signal.donations.GooglePayPaymentSource
|
||||
import org.signal.donations.InAppPaymentType
|
||||
import org.signal.donations.PaymentSourceType
|
||||
import org.signal.donations.StripeApi
|
||||
import org.signal.donations.StripeIntentAccessor
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.DonationSerializationHelper.toFiatMoney
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.InAppPaymentsRepository
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.InAppPaymentsRepository.requireSubscriberType
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.InAppPaymentsRepository.toErrorSource
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.InAppPaymentsRepository.toPaymentSourceType
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.OneTimeInAppPaymentRepository
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.RecurringInAppPaymentRepository
|
||||
@@ -30,8 +33,6 @@ import org.thoughtcrime.securesms.database.InAppPaymentTable
|
||||
import org.thoughtcrime.securesms.database.model.InAppPaymentSubscriberRecord
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.InAppPaymentData
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies
|
||||
import org.thoughtcrime.securesms.jobs.MultiDeviceSubscriptionSyncRequestJob
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.thoughtcrime.securesms.util.rx.RxStore
|
||||
@@ -76,7 +77,7 @@ class StripePaymentInProgressViewModel(
|
||||
}
|
||||
|
||||
fun processNewDonation(inAppPayment: InAppPaymentTable.InAppPayment, nextActionHandler: StripeNextActionHandler) {
|
||||
Log.d(TAG, "Proceeding with donation...", true)
|
||||
Log.d(TAG, "Proceeding with InAppPayment::${inAppPayment.id} of type ${inAppPayment.type}...", true)
|
||||
|
||||
val paymentSourceProvider: PaymentSourceProvider = resolvePaymentSourceProvider(inAppPayment.type.toErrorSource())
|
||||
|
||||
@@ -145,7 +146,7 @@ class StripePaymentInProgressViewModel(
|
||||
private fun proceedMonthly(inAppPayment: InAppPaymentTable.InAppPayment, paymentSourceProvider: PaymentSourceProvider, nextActionHandler: StripeNextActionHandler) {
|
||||
val ensureSubscriberId: Completable = recurringInAppPaymentRepository.ensureSubscriberId(inAppPayment.type.requireSubscriberType())
|
||||
val createAndConfirmSetupIntent: Single<StripeApi.Secure3DSAction> = paymentSourceProvider.paymentSource.flatMap {
|
||||
stripeRepository.createAndConfirmSetupIntent(it, paymentSourceProvider.paymentSourceType as PaymentSourceType.Stripe)
|
||||
stripeRepository.createAndConfirmSetupIntent(inAppPayment.type, it, paymentSourceProvider.paymentSourceType as PaymentSourceType.Stripe)
|
||||
}
|
||||
|
||||
val setLevel: Completable = recurringInAppPaymentRepository.setSubscriptionLevel(inAppPayment, paymentSourceProvider.paymentSourceType)
|
||||
@@ -171,7 +172,7 @@ class StripePaymentInProgressViewModel(
|
||||
)
|
||||
.flatMap { secure3DSResult -> stripeRepository.getStatusAndPaymentMethodId(secure3DSResult, secure3DSAction.paymentMethodId) }
|
||||
}
|
||||
.flatMapCompletable { stripeRepository.setDefaultPaymentMethod(it.paymentMethod!!, it.intentId, paymentSourceProvider.paymentSourceType) }
|
||||
.flatMapCompletable { stripeRepository.setDefaultPaymentMethod(it.paymentMethod!!, it.intentId, inAppPayment.type.requireSubscriberType(), paymentSourceProvider.paymentSourceType) }
|
||||
.onErrorResumeNext {
|
||||
when (it) {
|
||||
is DonationError -> Completable.error(it)
|
||||
@@ -202,7 +203,7 @@ class StripePaymentInProgressViewModel(
|
||||
|
||||
val amount = inAppPayment.data.amount!!.toFiatMoney()
|
||||
val recipientId = inAppPayment.data.recipientId?.let { RecipientId.from(it) } ?: Recipient.self().id
|
||||
val verifyUser = if (inAppPayment.type == InAppPaymentTable.Type.ONE_TIME_GIFT) {
|
||||
val verifyUser = if (inAppPayment.type == InAppPaymentType.ONE_TIME_GIFT) {
|
||||
OneTimeInAppPaymentRepository.verifyRecipientIsAllowedToReceiveAGift(recipientId)
|
||||
} else {
|
||||
Completable.complete()
|
||||
@@ -257,9 +258,6 @@ class StripePaymentInProgressViewModel(
|
||||
disposables += recurringInAppPaymentRepository.cancelActiveSubscription(subscriberType).subscribeBy(
|
||||
onComplete = {
|
||||
Log.d(TAG, "Cancellation succeeded", true)
|
||||
SignalStore.donationsValues().updateLocalStateForManualCancellation(subscriberType)
|
||||
MultiDeviceSubscriptionSyncRequestJob.enqueue()
|
||||
stripeRepository.scheduleSyncForAccountRecordChange()
|
||||
store.update { DonationProcessorStage.COMPLETE }
|
||||
},
|
||||
onError = { throwable ->
|
||||
|
||||
@@ -55,8 +55,8 @@ import org.signal.core.ui.theme.SignalTheme
|
||||
import org.signal.core.util.getParcelableCompat
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.components.TemporaryScreenshotSecurity
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.DonationPaymentComponent
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.DonationSerializationHelper.toFiatMoney
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.InAppPaymentComponent
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.donate.DonationCheckoutDelegate
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.donate.DonationProcessorAction
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.donate.DonationProcessorActionResult
|
||||
@@ -80,9 +80,9 @@ class BankTransferDetailsFragment : ComposeFragment(), DonationCheckoutDelegate.
|
||||
private val viewModel: BankTransferDetailsViewModel by viewModels()
|
||||
|
||||
private val stripePaymentViewModel: StripePaymentInProgressViewModel by navGraphViewModels(
|
||||
R.id.donate_to_signal,
|
||||
R.id.checkout_flow,
|
||||
factoryProducer = {
|
||||
StripePaymentInProgressViewModel.Factory(requireListener<DonationPaymentComponent>().stripeRepository)
|
||||
StripePaymentInProgressViewModel.Factory(requireListener<InAppPaymentComponent>().stripeRepository)
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -56,8 +56,8 @@ import org.signal.core.ui.Texts
|
||||
import org.signal.core.util.getParcelableCompat
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.components.TemporaryScreenshotSecurity
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.DonationPaymentComponent
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.DonationSerializationHelper.toFiatMoney
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.InAppPaymentComponent
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.donate.DonationCheckoutDelegate
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.donate.DonationProcessorAction
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.donate.DonationProcessorActionResult
|
||||
@@ -84,9 +84,9 @@ class IdealTransferDetailsFragment : ComposeFragment(), DonationCheckoutDelegate
|
||||
}
|
||||
|
||||
private val stripePaymentViewModel: StripePaymentInProgressViewModel by navGraphViewModels(
|
||||
R.id.donate_to_signal,
|
||||
R.id.checkout_flow,
|
||||
factoryProducer = {
|
||||
StripePaymentInProgressViewModel.Factory(requireListener<DonationPaymentComponent>().stripeRepository)
|
||||
StripePaymentInProgressViewModel.Factory(requireListener<InAppPaymentComponent>().stripeRepository)
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.components.settings.app.subscription.errors
|
||||
|
||||
import android.content.Context
|
||||
import androidx.annotation.StringRes
|
||||
import org.signal.donations.InAppPaymentType
|
||||
import org.signal.donations.PaymentSourceType
|
||||
import org.signal.donations.StripeDeclineCode
|
||||
import org.signal.donations.StripeFailureCode
|
||||
@@ -38,7 +39,7 @@ class DonationErrorParams<V> private constructor(
|
||||
is DonationError.BadgeRedemptionError.TimeoutWaitingForTokenError -> getStillProcessingErrorParams(context, callback)
|
||||
is DonationError.BadgeRedemptionError.FailedToValidateCredentialError -> getBadgeCredentialValidationErrorParams(context, callback)
|
||||
is DonationError.BadgeRedemptionError.GenericError -> getGenericRedemptionError(context, throwable.source.toInAppPaymentType(), callback)
|
||||
else -> getGenericRedemptionError(context, InAppPaymentTable.Type.ONE_TIME_DONATION, callback)
|
||||
else -> getGenericRedemptionError(context, InAppPaymentType.ONE_TIME_DONATION, callback)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,9 +82,9 @@ class DonationErrorParams<V> private constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private fun <V> getGenericRedemptionError(context: Context, type: InAppPaymentTable.Type, callback: Callback<V>): DonationErrorParams<V> {
|
||||
private fun <V> getGenericRedemptionError(context: Context, type: InAppPaymentType, callback: Callback<V>): DonationErrorParams<V> {
|
||||
return when (type) {
|
||||
InAppPaymentTable.Type.ONE_TIME_GIFT -> DonationErrorParams(
|
||||
InAppPaymentType.ONE_TIME_GIFT -> DonationErrorParams(
|
||||
title = R.string.DonationsErrors__donation_failed,
|
||||
message = R.string.DonationsErrors__your_payment_was_processed_but,
|
||||
positiveAction = callback.onContactSupport(context),
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package org.thoughtcrime.securesms.components.settings.app.subscription.manage
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.text.SpannableStringBuilder
|
||||
import android.view.View
|
||||
@@ -11,9 +10,9 @@ import androidx.navigation.fragment.findNavController
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import org.signal.core.util.dp
|
||||
import org.signal.core.util.money.FiatMoney
|
||||
import org.signal.donations.InAppPaymentType
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.badges.gifts.ExpiredGiftSheet
|
||||
import org.thoughtcrime.securesms.badges.gifts.flow.GiftFlowActivity
|
||||
import org.thoughtcrime.securesms.badges.models.BadgePreview
|
||||
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
|
||||
import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment
|
||||
@@ -26,7 +25,6 @@ import org.thoughtcrime.securesms.components.settings.app.subscription.completed
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.models.NetworkFailure
|
||||
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.PendingOneTimeDonation
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies
|
||||
@@ -167,7 +165,7 @@ class ManageDonationsFragment :
|
||||
primaryWrappedButton(
|
||||
text = DSLSettingsText.from(R.string.ManageDonationsFragment__donate_to_signal),
|
||||
onClick = {
|
||||
findNavController().safeNavigate(ManageDonationsFragmentDirections.actionManageDonationsFragmentToDonateToSignalFragment(InAppPaymentTable.Type.ONE_TIME_DONATION))
|
||||
findNavController().safeNavigate(ManageDonationsFragmentDirections.actionManageDonationsFragmentToDonateToSignalFragment(InAppPaymentType.ONE_TIME_DONATION))
|
||||
}
|
||||
)
|
||||
|
||||
@@ -277,7 +275,7 @@ class ManageDonationsFragment :
|
||||
subscriberRequiresCancel = state.subscriberRequiresCancel,
|
||||
onRowClick = {
|
||||
if (it != ManageDonationsState.RedemptionState.IN_PROGRESS) {
|
||||
findNavController().safeNavigate(ManageDonationsFragmentDirections.actionManageDonationsFragmentToDonateToSignalFragment(InAppPaymentTable.Type.RECURRING_DONATION))
|
||||
findNavController().safeNavigate(ManageDonationsFragmentDirections.actionManageDonationsFragmentToDonateToSignalFragment(InAppPaymentType.RECURRING_DONATION))
|
||||
}
|
||||
},
|
||||
onPendingClick = {
|
||||
@@ -345,7 +343,7 @@ class ManageDonationsFragment :
|
||||
title = DSLSettingsText.from(R.string.ManageDonationsFragment__donate_for_a_friend),
|
||||
icon = DSLSettingsIcon.from(R.drawable.symbol_gift_24),
|
||||
onClick = {
|
||||
startActivity(Intent(requireContext(), GiftFlowActivity::class.java))
|
||||
findNavController().safeNavigate(ManageDonationsFragmentDirections.actionManageDonationsFragmentToDonateToSignalFragment(InAppPaymentType.ONE_TIME_GIFT))
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -445,6 +443,6 @@ class ManageDonationsFragment :
|
||||
}
|
||||
|
||||
override fun onMakeAMonthlyDonation() {
|
||||
findNavController().safeNavigate(ManageDonationsFragmentDirections.actionManageDonationsFragmentToDonateToSignalFragment(InAppPaymentTable.Type.RECURRING_DONATION))
|
||||
findNavController().safeNavigate(ManageDonationsFragmentDirections.actionManageDonationsFragmentToDonateToSignalFragment(InAppPaymentType.RECURRING_DONATION))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,9 +12,9 @@ import io.reactivex.rxjava3.kotlin.subscribeBy
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.signal.core.util.orNull
|
||||
import org.signal.donations.InAppPaymentType
|
||||
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
|
||||
@@ -87,7 +87,7 @@ class ManageDonationsViewModel(
|
||||
store.update { it.copy(hasReceipts = hasReceipts) }
|
||||
}
|
||||
|
||||
disposables += InAppPaymentsRepository.observeInAppPaymentRedemption(InAppPaymentTable.Type.RECURRING_DONATION).subscribeBy { redemptionStatus ->
|
||||
disposables += InAppPaymentsRepository.observeInAppPaymentRedemption(InAppPaymentType.RECURRING_DONATION).subscribeBy { redemptionStatus ->
|
||||
store.update { manageDonationsState ->
|
||||
manageDonationsState.copy(
|
||||
nonVerifiedMonthlyDonation = if (redemptionStatus is DonationRedemptionJobStatus.PendingExternalVerification) redemptionStatus.nonVerifiedMonthlyDonation else null,
|
||||
@@ -98,7 +98,7 @@ class ManageDonationsViewModel(
|
||||
|
||||
disposables += Observable.combineLatest(
|
||||
SignalStore.donationsValues().observablePendingOneTimeDonation,
|
||||
InAppPaymentsRepository.observeInAppPaymentRedemption(InAppPaymentTable.Type.ONE_TIME_DONATION)
|
||||
InAppPaymentsRepository.observeInAppPaymentRedemption(InAppPaymentType.ONE_TIME_DONATION)
|
||||
) { pendingFromStore, pendingFromJob ->
|
||||
if (pendingFromStore.isPresent) {
|
||||
pendingFromStore
|
||||
|
||||
Reference in New Issue
Block a user