kopia lustrzana https://github.com/ryukoposting/Signal-Android
Add improved handling for credit card errors.
rodzic
643206b946
commit
40cf87307a
|
@ -87,7 +87,7 @@ class GiftFlowConfirmationFragment :
|
|||
|
||||
keyboardPagerViewModel.setOnlyPage(KeyboardPage.EMOJI)
|
||||
|
||||
donationCheckoutDelegate = DonationCheckoutDelegate(this, this)
|
||||
donationCheckoutDelegate = DonationCheckoutDelegate(this, this, DonationErrorSource.GIFT)
|
||||
|
||||
processingDonationPaymentDialog = MaterialAlertDialogBuilder(requireContext())
|
||||
.setView(R.layout.processing_payment_dialog)
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
package org.thoughtcrime.securesms.components.settings.app.subscription.donate
|
||||
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import android.text.SpannableStringBuilder
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
|
@ -16,7 +14,6 @@ import androidx.navigation.fragment.navArgs
|
|||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.airbnb.lottie.LottieAnimationView
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||
import org.signal.core.util.dp
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.signal.core.util.money.FiatMoney
|
||||
|
@ -30,9 +27,6 @@ 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.GatewayRequest
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.errors.DonationError
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.errors.DonationErrorDialogs
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.errors.DonationErrorParams
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.errors.DonationErrorSource
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.models.CurrencySelection
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.models.NetworkFailure
|
||||
|
@ -80,8 +74,6 @@ class DonateToSignalFragment :
|
|||
}
|
||||
}
|
||||
|
||||
private var errorDialog: DialogInterface? = null
|
||||
|
||||
private val args: DonateToSignalFragmentArgs by navArgs()
|
||||
private val viewModel: DonateToSignalViewModel by viewModels(factoryProducer = {
|
||||
DonateToSignalViewModel.Factory(args.startType)
|
||||
|
@ -114,7 +106,7 @@ class DonateToSignalFragment :
|
|||
}
|
||||
|
||||
override fun bindAdapter(adapter: MappingAdapter) {
|
||||
donationCheckoutDelegate = DonationCheckoutDelegate(this, this)
|
||||
donationCheckoutDelegate = DonationCheckoutDelegate(this, this, DonationErrorSource.BOOST, DonationErrorSource.SUBSCRIPTION)
|
||||
|
||||
val recyclerView = this.recyclerView!!
|
||||
recyclerView.overScrollMode = RecyclerView.OVER_SCROLL_IF_CONTENT_SCROLLS
|
||||
|
@ -139,19 +131,6 @@ class DonateToSignalFragment :
|
|||
DonationPillToggle.register(adapter)
|
||||
|
||||
disposables.bindTo(viewLifecycleOwner)
|
||||
|
||||
disposables += DonationError.getErrorsForSource(DonationErrorSource.BOOST)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe { error ->
|
||||
showErrorDialog(error)
|
||||
}
|
||||
|
||||
disposables += DonationError.getErrorsForSource(DonationErrorSource.SUBSCRIPTION)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe { error ->
|
||||
showErrorDialog(error)
|
||||
}
|
||||
|
||||
disposables += viewModel.actions.subscribe { action ->
|
||||
when (action) {
|
||||
is DonateToSignalAction.DisplayCurrencySelectionDialog -> {
|
||||
|
@ -389,36 +368,6 @@ class DonateToSignalFragment :
|
|||
}
|
||||
}
|
||||
|
||||
private fun showErrorDialog(throwable: Throwable) {
|
||||
if (errorDialog != null) {
|
||||
Log.d(TAG, "Already displaying an error dialog. Skipping.", throwable, true)
|
||||
} else {
|
||||
Log.d(TAG, "Displaying donation error dialog.", true)
|
||||
errorDialog = DonationErrorDialogs.show(
|
||||
requireContext(), throwable,
|
||||
object : DonationErrorDialogs.DialogCallback() {
|
||||
var tryCCAgain = false
|
||||
|
||||
override fun onTryCreditCardAgain(context: Context): DonationErrorParams.ErrorAction<Unit>? {
|
||||
return DonationErrorParams.ErrorAction(
|
||||
label = R.string.DeclineCode__try,
|
||||
action = {
|
||||
tryCCAgain = true
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
override fun onDialogDismissed() {
|
||||
errorDialog = null
|
||||
if (!tryCCAgain) {
|
||||
findNavController().popBackStack()
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun startAnimationAboveSelectedBoost(view: View) {
|
||||
val animationView = getAnimationContainer(view)
|
||||
val viewProjection = Projection.relativeToViewRoot(view, null)
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package org.thoughtcrime.securesms.components.settings.app.subscription.donate
|
||||
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.setFragmentResultListener
|
||||
import androidx.fragment.app.viewModels
|
||||
|
@ -10,6 +12,8 @@ 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.disposables.Disposable
|
||||
import io.reactivex.rxjava3.kotlin.subscribeBy
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.signal.core.util.money.FiatMoney
|
||||
|
@ -18,7 +22,6 @@ import org.thoughtcrime.securesms.R
|
|||
import org.thoughtcrime.securesms.components.settings.app.subscription.DonationPaymentComponent
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.InAppDonations
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.donate.card.CreditCardFragment
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.donate.card.CreditCardResult
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.donate.gateway.GatewayRequest
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.donate.gateway.GatewayResponse
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.donate.gateway.GatewaySelectorBottomSheet
|
||||
|
@ -26,6 +29,8 @@ import org.thoughtcrime.securesms.components.settings.app.subscription.donate.pa
|
|||
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.components.settings.app.subscription.errors.DonationError
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.errors.DonationErrorDialogs
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.errors.DonationErrorParams
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.errors.DonationErrorSource
|
||||
import org.thoughtcrime.securesms.util.LifecycleDisposable
|
||||
import org.thoughtcrime.securesms.util.fragments.requireListener
|
||||
|
@ -36,7 +41,9 @@ import java.util.Currency
|
|||
*/
|
||||
class DonationCheckoutDelegate(
|
||||
private val fragment: Fragment,
|
||||
private val callback: Callback
|
||||
private val callback: Callback,
|
||||
errorSource: DonationErrorSource,
|
||||
vararg additionalSources: DonationErrorSource
|
||||
) : DefaultLifecycleObserver {
|
||||
|
||||
companion object {
|
||||
|
@ -57,6 +64,7 @@ class DonationCheckoutDelegate(
|
|||
|
||||
init {
|
||||
fragment.viewLifecycleOwner.lifecycle.addObserver(this)
|
||||
ErrorHandler().attach(fragment, errorSource, *additionalSources)
|
||||
}
|
||||
|
||||
override fun onCreate(owner: LifecycleOwner) {
|
||||
|
@ -75,8 +83,8 @@ class DonationCheckoutDelegate(
|
|||
}
|
||||
|
||||
fragment.setFragmentResultListener(CreditCardFragment.REQUEST_KEY) { _, bundle ->
|
||||
val result: CreditCardResult = bundle.getParcelable(CreditCardFragment.REQUEST_KEY)!!
|
||||
handleCreditCardResult(result)
|
||||
val result: DonationProcessorActionResult = bundle.getParcelable(StripePaymentInProgressFragment.REQUEST_KEY)!!
|
||||
handleDonationProcessorActionResult(result)
|
||||
}
|
||||
|
||||
fragment.setFragmentResultListener(PayPalPaymentInProgressFragment.REQUEST_KEY) { _, bundle ->
|
||||
|
@ -97,12 +105,6 @@ class DonationCheckoutDelegate(
|
|||
}
|
||||
}
|
||||
|
||||
private fun handleCreditCardResult(creditCardResult: CreditCardResult) {
|
||||
Log.d(TAG, "Received credit card information from fragment.")
|
||||
stripePaymentViewModel.provideCardData(creditCardResult.creditCardData)
|
||||
callback.navigateToStripePaymentInProgress(creditCardResult.gatewayRequest)
|
||||
}
|
||||
|
||||
private fun handleDonationProcessorActionResult(result: DonationProcessorActionResult) {
|
||||
when (result.status) {
|
||||
DonationProcessorActionResult.Status.SUCCESS -> handleSuccessfulDonationProcessorActionResult(result)
|
||||
|
@ -194,6 +196,71 @@ class DonationCheckoutDelegate(
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared logic for handling checkout errors.
|
||||
*/
|
||||
class ErrorHandler : DefaultLifecycleObserver {
|
||||
|
||||
private var fragment: Fragment? = null
|
||||
private var errorDialog: DialogInterface? = null
|
||||
|
||||
fun attach(fragment: Fragment, errorSource: DonationErrorSource, vararg additionalSources: DonationErrorSource) {
|
||||
this.fragment = fragment
|
||||
val disposables = LifecycleDisposable()
|
||||
fragment.viewLifecycleOwner.lifecycle.addObserver(this)
|
||||
|
||||
disposables.bindTo(fragment.viewLifecycleOwner)
|
||||
disposables += registerErrorSource(errorSource)
|
||||
additionalSources.forEach { source ->
|
||||
disposables += registerErrorSource(source)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy(owner: LifecycleOwner) {
|
||||
errorDialog?.dismiss()
|
||||
fragment = null
|
||||
}
|
||||
|
||||
private fun registerErrorSource(errorSource: DonationErrorSource): Disposable {
|
||||
return DonationError.getErrorsForSource(errorSource)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe { error ->
|
||||
showErrorDialog(error)
|
||||
}
|
||||
}
|
||||
|
||||
private fun showErrorDialog(throwable: Throwable) {
|
||||
if (errorDialog != null) {
|
||||
Log.d(TAG, "Already displaying an error dialog. Skipping.", throwable, true)
|
||||
return
|
||||
}
|
||||
|
||||
Log.d(TAG, "Displaying donation error dialog.", true)
|
||||
errorDialog = DonationErrorDialogs.show(
|
||||
fragment!!.requireContext(), throwable,
|
||||
object : DonationErrorDialogs.DialogCallback() {
|
||||
var tryCCAgain = false
|
||||
|
||||
override fun onTryCreditCardAgain(context: Context): DonationErrorParams.ErrorAction<Unit>? {
|
||||
return DonationErrorParams.ErrorAction(
|
||||
label = R.string.DeclineCode__try,
|
||||
action = {
|
||||
tryCCAgain = true
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
override fun onDialogDismissed() {
|
||||
errorDialog = null
|
||||
if (!tryCCAgain) {
|
||||
fragment!!.findNavController().popBackStack()
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
interface Callback {
|
||||
fun navigateToStripePaymentInProgress(gatewayRequest: GatewayRequest)
|
||||
fun navigateToPayPalPaymentInProgress(gatewayRequest: GatewayRequest)
|
||||
|
|
|
@ -7,21 +7,30 @@ import android.view.WindowManager
|
|||
import android.view.inputmethod.EditorInfo
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.core.widget.addTextChangedListener
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.setFragmentResult
|
||||
import androidx.fragment.app.setFragmentResultListener
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.navigation.fragment.navArgs
|
||||
import androidx.navigation.navGraphViewModels
|
||||
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.donate.DonateToSignalType
|
||||
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.components.settings.app.subscription.errors.DonationErrorSource
|
||||
import org.thoughtcrime.securesms.databinding.CreditCardFragmentBinding
|
||||
import org.thoughtcrime.securesms.payments.FiatMoneyUtil
|
||||
import org.thoughtcrime.securesms.util.LifecycleDisposable
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||
import org.thoughtcrime.securesms.util.ViewUtil
|
||||
import org.thoughtcrime.securesms.util.fragments.requireListener
|
||||
import org.thoughtcrime.securesms.util.navigation.safeNavigate
|
||||
|
||||
class CreditCardFragment : Fragment(R.layout.credit_card_fragment) {
|
||||
|
@ -30,8 +39,30 @@ class CreditCardFragment : Fragment(R.layout.credit_card_fragment) {
|
|||
private val args: CreditCardFragmentArgs by navArgs()
|
||||
private val viewModel: CreditCardViewModel by viewModels()
|
||||
private val lifecycleDisposable = LifecycleDisposable()
|
||||
private val stripePaymentViewModel: StripePaymentInProgressViewModel by navGraphViewModels(
|
||||
R.id.donate_to_signal,
|
||||
factoryProducer = {
|
||||
StripePaymentInProgressViewModel.Factory(requireListener<DonationPaymentComponent>().stripeRepository)
|
||||
}
|
||||
)
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
val errorSource: DonationErrorSource = when (args.request.donateToSignalType) {
|
||||
DonateToSignalType.ONE_TIME -> DonationErrorSource.BOOST
|
||||
DonateToSignalType.MONTHLY -> DonationErrorSource.SUBSCRIPTION
|
||||
DonateToSignalType.GIFT -> DonationErrorSource.GIFT
|
||||
}
|
||||
|
||||
DonationCheckoutDelegate.ErrorHandler().attach(this, errorSource)
|
||||
|
||||
setFragmentResultListener(StripePaymentInProgressFragment.REQUEST_KEY) { _, bundle ->
|
||||
val result: DonationProcessorActionResult = bundle.getParcelable(StripePaymentInProgressFragment.REQUEST_KEY)!!
|
||||
if (result.status == DonationProcessorActionResult.Status.SUCCESS) {
|
||||
findNavController().popBackStack()
|
||||
setFragmentResult(REQUEST_KEY, bundle)
|
||||
}
|
||||
}
|
||||
|
||||
binding.title.text = if (args.request.donateToSignalType == DonateToSignalType.MONTHLY) {
|
||||
getString(
|
||||
R.string.CreditCardFragment__donation_amount_s_per_month,
|
||||
|
@ -85,16 +116,13 @@ class CreditCardFragment : Fragment(R.layout.credit_card_fragment) {
|
|||
}
|
||||
|
||||
binding.continueButton.setOnClickListener {
|
||||
findNavController().popBackStack()
|
||||
|
||||
val resultBundle = bundleOf(
|
||||
REQUEST_KEY to CreditCardResult(
|
||||
args.request,
|
||||
viewModel.getCardData()
|
||||
stripePaymentViewModel.provideCardData(viewModel.getCardData())
|
||||
findNavController().safeNavigate(
|
||||
CreditCardFragmentDirections.actionCreditCardFragmentToStripePaymentInProgressFragment(
|
||||
DonationProcessorAction.PROCESS_NEW_DONATION,
|
||||
args.request
|
||||
)
|
||||
)
|
||||
|
||||
setFragmentResult(REQUEST_KEY, resultBundle)
|
||||
}
|
||||
|
||||
binding.toolbar.setNavigationOnClickListener {
|
||||
|
@ -195,7 +223,7 @@ class CreditCardFragment : Fragment(R.layout.credit_card_fragment) {
|
|||
}
|
||||
|
||||
companion object {
|
||||
val REQUEST_KEY = "card.data"
|
||||
const val REQUEST_KEY = "card.result"
|
||||
|
||||
private val NO_ERROR = ErrorState(false, -1)
|
||||
}
|
||||
|
|
|
@ -130,6 +130,9 @@
|
|||
<action
|
||||
android:id="@+id/action_creditCardFragment_to_yourInformationIsPrivateBottomSheet"
|
||||
app:destination="@id/yourInformationIsPrivateBottomSheet" />
|
||||
<action
|
||||
android:id="@+id/action_creditCardFragment_to_stripePaymentInProgressFragment"
|
||||
app:destination="@id/stripePaymentInProgressFragment" />
|
||||
</fragment>
|
||||
|
||||
<dialog
|
||||
|
|
|
@ -111,6 +111,9 @@
|
|||
<action
|
||||
android:id="@+id/action_creditCardFragment_to_yourInformationIsPrivateBottomSheet"
|
||||
app:destination="@id/yourInformationIsPrivateBottomSheet" />
|
||||
<action
|
||||
android:id="@+id/action_creditCardFragment_to_stripePaymentInProgressFragment"
|
||||
app:destination="@id/stripePaymentInProgressFragment" />
|
||||
</fragment>
|
||||
|
||||
<dialog
|
||||
|
|
Ładowanie…
Reference in New Issue