Add SEPA max amount exceeded dialog.

This commit is contained in:
Cody Henthorne 2023-11-10 15:05:52 -05:00
parent 43a13964bd
commit 95d7d26f11
10 changed files with 74 additions and 15 deletions

View file

@ -14,6 +14,7 @@ import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.subjects.PublishSubject
import org.signal.core.util.concurrent.LifecycleDisposable
import org.signal.core.util.logging.Log
import org.signal.core.util.money.FiatMoney
import org.thoughtcrime.securesms.MainActivity
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.components.InputAwareLayout
@ -286,6 +287,8 @@ class GiftFlowConfirmationFragment :
override fun onProcessorActionProcessed() = Unit
override fun showSepaEuroMaximumDialog(sepaEuroMaximum: FiatMoney) = error("Unsupported operation")
override fun onUserLaunchedAnExternalApplication() = Unit
override fun navigateToDonationPending(gatewayRequest: GatewayRequest) = error("Unsupported operation")

View file

@ -470,6 +470,15 @@ class DonateToSignalFragment :
viewModel.refreshActiveSubscription()
}
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() = Unit
override fun navigateToDonationPending(gatewayRequest: GatewayRequest) {

View file

@ -36,7 +36,9 @@ import org.thoughtcrime.securesms.components.settings.app.subscription.errors.Do
import org.thoughtcrime.securesms.components.settings.app.subscription.errors.DonationErrorParams
import org.thoughtcrime.securesms.components.settings.app.subscription.errors.DonationErrorSource
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.payments.currency.CurrencyUtil
import org.thoughtcrime.securesms.util.fragments.requireListener
import java.math.BigDecimal
import java.util.Currency
/**
@ -77,8 +79,12 @@ class DonationCheckoutDelegate(
registerGooglePayCallback()
fragment.setFragmentResultListener(GatewaySelectorBottomSheet.REQUEST_KEY) { _, bundle ->
val response: GatewayResponse = bundle.getParcelableCompat(GatewaySelectorBottomSheet.REQUEST_KEY, GatewayResponse::class.java)!!
handleGatewaySelectionResponse(response)
if (bundle.containsKey(GatewaySelectorBottomSheet.FAILURE_KEY)) {
callback.showSepaEuroMaximumDialog(FiatMoney(bundle.getSerializable(GatewaySelectorBottomSheet.SEPA_EURO_MAX) as BigDecimal, CurrencyUtil.EURO))
} else {
val response: GatewayResponse = bundle.getParcelableCompat(GatewaySelectorBottomSheet.REQUEST_KEY, GatewayResponse::class.java)!!
handleGatewaySelectionResponse(response)
}
}
fragment.setFragmentResultListener(StripePaymentInProgressFragment.REQUEST_KEY) { _, bundle ->
@ -342,5 +348,6 @@ class DonationCheckoutDelegate(
fun navigateToBankTransferMandate(gatewayResponse: GatewayResponse)
fun onPaymentComplete(gatewayRequest: GatewayRequest)
fun onProcessorActionProcessed()
fun showSepaEuroMaximumDialog(sepaEuroMaximum: FiatMoney)
}
}

View file

@ -24,6 +24,7 @@ import org.thoughtcrime.securesms.components.settings.app.subscription.models.Pa
import org.thoughtcrime.securesms.components.settings.configure
import org.thoughtcrime.securesms.components.settings.models.IndeterminateLoadingCircle
import org.thoughtcrime.securesms.payments.FiatMoneyUtil
import org.thoughtcrime.securesms.payments.currency.CurrencyUtil
import org.thoughtcrime.securesms.util.fragments.requireListener
/**
@ -140,9 +141,18 @@ class GatewaySelectorBottomSheet : DSLSettingsBottomSheetFragment() {
text = DSLSettingsText.from(R.string.GatewaySelectorBottomSheet__bank_transfer),
icon = DSLSettingsIcon.from(R.drawable.bank_transfer),
onClick = {
findNavController().popBackStack()
val response = GatewayResponse(GatewayResponse.Gateway.SEPA_DEBIT, args.request)
setFragmentResult(REQUEST_KEY, bundleOf(REQUEST_KEY to response))
if (state.sepaEuroMaximum != null &&
args.request.fiat.currency == CurrencyUtil.EURO &&
args.request.fiat.amount > state.sepaEuroMaximum.amount
) {
findNavController().popBackStack()
setFragmentResult(REQUEST_KEY, bundleOf(FAILURE_KEY to true, SEPA_EURO_MAX to state.sepaEuroMaximum.amount))
} else {
findNavController().popBackStack()
val response = GatewayResponse(GatewayResponse.Gateway.SEPA_DEBIT, args.request)
setFragmentResult(REQUEST_KEY, bundleOf(REQUEST_KEY to response))
}
}
)
}
@ -164,6 +174,8 @@ class GatewaySelectorBottomSheet : DSLSettingsBottomSheetFragment() {
companion object {
const val REQUEST_KEY = "payment_checkout_mode"
const val FAILURE_KEY = "gateway_failure"
const val SEPA_EURO_MAX = "sepa_euro_max"
fun DSLConfiguration.presentTitleAndSubtitle(context: Context, request: GatewayRequest) {
when (request.donateToSignalType) {

View file

@ -1,7 +1,9 @@
package org.thoughtcrime.securesms.components.settings.app.subscription.donate.gateway
import io.reactivex.rxjava3.core.Single
import org.signal.core.util.money.FiatMoney
import org.thoughtcrime.securesms.components.settings.app.subscription.getAvailablePaymentMethods
import org.thoughtcrime.securesms.payments.currency.CurrencyUtil
import org.whispersystems.signalservice.api.services.DonationsService
import org.whispersystems.signalservice.internal.push.DonationsConfiguration
import java.util.Locale
@ -9,12 +11,12 @@ import java.util.Locale
class GatewaySelectorRepository(
private val donationsService: DonationsService
) {
fun getAvailableGateways(currencyCode: String): Single<Set<GatewayResponse.Gateway>> {
fun getAvailableGatewayConfiguration(currencyCode: String): Single<GatewayConfiguration> {
return Single.fromCallable {
donationsService.getDonationsConfiguration(Locale.getDefault())
}.flatMap { it.flattenResult() }
.map { configuration ->
configuration.getAvailablePaymentMethods(currencyCode).map {
val available = configuration.getAvailablePaymentMethods(currencyCode).map {
when (it) {
DonationsConfiguration.PAYPAL -> listOf(GatewayResponse.Gateway.PAYPAL)
DonationsConfiguration.CARD -> listOf(GatewayResponse.Gateway.CREDIT_CARD, GatewayResponse.Gateway.GOOGLE_PAY)
@ -23,6 +25,16 @@ class GatewaySelectorRepository(
else -> listOf()
}
}.flatten().toSet()
GatewayConfiguration(
availableGateways = available,
sepaEuroMaximum = if (configuration.sepaMaximumEuros != null) FiatMoney(configuration.sepaMaximumEuros, CurrencyUtil.EURO) else null
)
}
}
data class GatewayConfiguration(
val availableGateways: Set<GatewayResponse.Gateway>,
val sepaEuroMaximum: FiatMoney?
)
}

View file

@ -1,5 +1,6 @@
package org.thoughtcrime.securesms.components.settings.app.subscription.donate.gateway
import org.signal.core.util.money.FiatMoney
import org.thoughtcrime.securesms.badges.models.Badge
data class GatewaySelectorState(
@ -10,5 +11,6 @@ data class GatewaySelectorState(
val isPayPalAvailable: Boolean = false,
val isCreditCardAvailable: Boolean = false,
val isSEPADebitAvailable: Boolean = false,
val isIDEALAvailable: Boolean = false
val isIDEALAvailable: Boolean = false,
val sepaEuroMaximum: FiatMoney? = null
)

View file

@ -36,17 +36,18 @@ class GatewaySelectorViewModel(
init {
val isGooglePayAvailable = repository.isGooglePayAvailable().toSingleDefault(true).onErrorReturnItem(false)
val availabilitySet = gatewaySelectorRepository.getAvailableGateways(currencyCode = args.request.currencyCode)
disposables += Single.zip(isGooglePayAvailable, availabilitySet, ::Pair).subscribeBy { (googlePayAvailable, gatewaysAvailable) ->
val gatewayConfiguration = gatewaySelectorRepository.getAvailableGatewayConfiguration(currencyCode = args.request.currencyCode)
disposables += Single.zip(isGooglePayAvailable, gatewayConfiguration, ::Pair).subscribeBy { (googlePayAvailable, gatewayConfiguration) ->
SignalStore.donationsValues().isGooglePayReady = googlePayAvailable
store.update {
it.copy(
loading = false,
isCreditCardAvailable = it.isCreditCardAvailable && gatewaysAvailable.contains(GatewayResponse.Gateway.CREDIT_CARD),
isGooglePayAvailable = it.isGooglePayAvailable && googlePayAvailable && gatewaysAvailable.contains(GatewayResponse.Gateway.GOOGLE_PAY),
isPayPalAvailable = it.isPayPalAvailable && gatewaysAvailable.contains(GatewayResponse.Gateway.PAYPAL),
isSEPADebitAvailable = it.isSEPADebitAvailable && gatewaysAvailable.contains(GatewayResponse.Gateway.SEPA_DEBIT),
isIDEALAvailable = it.isIDEALAvailable && gatewaysAvailable.contains(GatewayResponse.Gateway.IDEAL)
isCreditCardAvailable = it.isCreditCardAvailable && gatewayConfiguration.availableGateways.contains(GatewayResponse.Gateway.CREDIT_CARD),
isGooglePayAvailable = it.isGooglePayAvailable && googlePayAvailable && gatewayConfiguration.availableGateways.contains(GatewayResponse.Gateway.GOOGLE_PAY),
isPayPalAvailable = it.isPayPalAvailable && gatewayConfiguration.availableGateways.contains(GatewayResponse.Gateway.PAYPAL),
isSEPADebitAvailable = it.isSEPADebitAvailable && gatewayConfiguration.availableGateways.contains(GatewayResponse.Gateway.SEPA_DEBIT),
isIDEALAvailable = it.isIDEALAvailable && gatewayConfiguration.availableGateways.contains(GatewayResponse.Gateway.IDEAL),
sepaEuroMaximum = gatewayConfiguration.sepaEuroMaximum
)
}
}

View file

@ -15,6 +15,8 @@ import java.util.Locale;
*/
public final class CurrencyUtil {
public static Currency EURO = Currency.getInstance("EUR");
public static @Nullable Currency getCurrencyByCurrencyCode(@NonNull String currencyCode) {
try {
return Currency.getInstance(currencyCode);

View file

@ -5855,6 +5855,10 @@
<string name="DonateToSignalFragment__your_payment_is_still_being_processed_onetime">Your donation is still being processed. This can take a few minutes depending on your connection. Please wait until this payment completes before making another donation.</string>
<!-- Dialog body when a user opens the manage donations main screen and they have a pending iDEAL donation -->
<string name="DonateToSignalFragment__your_ideal_payment_is_still_processing">Your iDEAL donation is still processing. Check your banking app to approve your payment before making another donation.</string>
<!-- Dialog title shown when a user tries to donate an amount higher than is allowed for a given payment method. -->
<string name="DonateToSignal__donation_amount_too_high">Donation amount too high</string>
<!-- Dialog body shown when a user tries to donate an amount higher than is allowed for a given payment method, place holder is the maximum -->
<string name="DonateToSignalFragment__you_can_send_up_to_s_via_bank_transfer">You can send up to %1$s via bank transfer. Try a different amount or a different payment method.</string>
<!-- Donation pill toggle monthly text -->
<string name="DonationPillToggle__monthly">Monthly</string>

View file

@ -31,6 +31,9 @@ public class DonationsConfiguration {
@JsonProperty("levels")
private Map<Integer, LevelConfiguration> levels;
@JsonProperty("sepaMaximumEuros")
private BigDecimal sepaMaximumEuros;
public static class CurrencyConfiguration {
@JsonProperty("minimum")
private BigDecimal minimum;
@ -84,4 +87,8 @@ public class DonationsConfiguration {
public Map<Integer, LevelConfiguration> getLevels() {
return levels;
}
public BigDecimal getSepaMaximumEuros() {
return sepaMaximumEuros;
}
}