diff --git a/app/src/main/java/org/thoughtcrime/securesms/badges/gifts/flow/GiftFlowConfirmationFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/badges/gifts/flow/GiftFlowConfirmationFragment.kt index b1fac2460c..cb9af5b98b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/badges/gifts/flow/GiftFlowConfirmationFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/badges/gifts/flow/GiftFlowConfirmationFragment.kt @@ -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") diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/donate/DonateToSignalFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/donate/DonateToSignalFragment.kt index ad1f568832..1e797693a9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/donate/DonateToSignalFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/donate/DonateToSignalFragment.kt @@ -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) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/donate/DonationCheckoutDelegate.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/donate/DonationCheckoutDelegate.kt index 13214494a5..25ec952516 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/donate/DonationCheckoutDelegate.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/donate/DonationCheckoutDelegate.kt @@ -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) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/donate/gateway/GatewaySelectorBottomSheet.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/donate/gateway/GatewaySelectorBottomSheet.kt index 1bd2aa1c1f..2e8909059f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/donate/gateway/GatewaySelectorBottomSheet.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/donate/gateway/GatewaySelectorBottomSheet.kt @@ -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) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/donate/gateway/GatewaySelectorRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/donate/gateway/GatewaySelectorRepository.kt index 64eeece9f2..981098c416 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/donate/gateway/GatewaySelectorRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/donate/gateway/GatewaySelectorRepository.kt @@ -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> { + fun getAvailableGatewayConfiguration(currencyCode: String): Single { 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, + val sepaEuroMaximum: FiatMoney? + ) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/donate/gateway/GatewaySelectorState.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/donate/gateway/GatewaySelectorState.kt index 6e05c2a333..6e3c65d7a2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/donate/gateway/GatewaySelectorState.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/donate/gateway/GatewaySelectorState.kt @@ -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 ) diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/donate/gateway/GatewaySelectorViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/donate/gateway/GatewaySelectorViewModel.kt index 422b40be76..a33d6d64f6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/donate/gateway/GatewaySelectorViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/donate/gateway/GatewaySelectorViewModel.kt @@ -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 ) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/payments/currency/CurrencyUtil.java b/app/src/main/java/org/thoughtcrime/securesms/payments/currency/CurrencyUtil.java index c640de5f32..3404688a0f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/payments/currency/CurrencyUtil.java +++ b/app/src/main/java/org/thoughtcrime/securesms/payments/currency/CurrencyUtil.java @@ -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); diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 806618908a..bad82c1d15 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -5855,6 +5855,10 @@ 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. Your iDEAL donation is still processing. Check your banking app to approve your payment before making another donation. + + Donation amount too high + + You can send up to %1$s via bank transfer. Try a different amount or a different payment method. Monthly diff --git a/libsignal-service/src/main/java/org/whispersystems/signalservice/internal/push/DonationsConfiguration.java b/libsignal-service/src/main/java/org/whispersystems/signalservice/internal/push/DonationsConfiguration.java index 0516631d4a..52a5fc81b2 100644 --- a/libsignal-service/src/main/java/org/whispersystems/signalservice/internal/push/DonationsConfiguration.java +++ b/libsignal-service/src/main/java/org/whispersystems/signalservice/internal/push/DonationsConfiguration.java @@ -31,6 +31,9 @@ public class DonationsConfiguration { @JsonProperty("levels") private Map levels; + @JsonProperty("sepaMaximumEuros") + private BigDecimal sepaMaximumEuros; + public static class CurrencyConfiguration { @JsonProperty("minimum") private BigDecimal minimum; @@ -84,4 +87,8 @@ public class DonationsConfiguration { public Map getLevels() { return levels; } + + public BigDecimal getSepaMaximumEuros() { + return sepaMaximumEuros; + } } \ No newline at end of file