diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/DonationsConfigurationExtensions.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/DonationsConfigurationExtensions.kt index afbb52f8c3..1f3b3ce90d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/DonationsConfigurationExtensions.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/DonationsConfigurationExtensions.kt @@ -95,11 +95,15 @@ fun DonationsConfiguration.getMinimumDonationAmounts(paymentMethodAvailability: .mapValues { FiatMoney(it.value.minimum, it.key) } } +fun DonationsConfiguration.getAvailablePaymentMethods(currencyCode: String): Set { + return currencies[currencyCode.lowercase()]?.supportedPaymentMethods ?: emptySet() +} + private fun DonationsConfiguration.getFilteredCurrencies(paymentMethodAvailability: PaymentMethodAvailability): Map { val userPaymentMethods = paymentMethodAvailability.toSet() val availableCurrencyCodes = PlatformCurrencyUtil.getAvailableCurrencyCodes() return currencies.filter { (code, config) -> - val areAllMethodsAvailable = config.supportedPaymentMethods.containsAll(userPaymentMethods) + val areAllMethodsAvailable = config.supportedPaymentMethods.any { it in userPaymentMethods } availableCurrencyCodes.contains(code.uppercase()) && areAllMethodsAvailable } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/donate/DonateToSignalViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/donate/DonateToSignalViewModel.kt index 12bb2388ca..3f85538177 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/donate/DonateToSignalViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/donate/DonateToSignalViewModel.kt @@ -241,6 +241,7 @@ class DonateToSignalViewModel( state.copy( oneTimeDonationState = state.oneTimeDonationState.copy( boosts = boostList, + selectedBoost = null, selectedCurrency = currency, donationStage = DonateToSignalState.DonationStage.READY, selectableCurrencyCodes = availableCurrencies.map(Currency::getCurrencyCode), 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 43a3603ab8..86cc6dc98b 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 @@ -21,6 +21,7 @@ import org.thoughtcrime.securesms.components.settings.app.subscription.donate.Do 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 +import org.thoughtcrime.securesms.components.settings.models.IndeterminateLoadingCircle import org.thoughtcrime.securesms.payments.FiatMoneyUtil import org.thoughtcrime.securesms.util.LifecycleDisposable import org.thoughtcrime.securesms.util.fragments.requireListener @@ -42,6 +43,7 @@ class GatewaySelectorBottomSheet : DSLSettingsBottomSheetFragment() { BadgeDisplay112.register(adapter) GooglePayButton.register(adapter) PayPalButton.register(adapter) + IndeterminateLoadingCircle.register(adapter) lifecycleDisposable.bindTo(viewLifecycleOwner) @@ -65,6 +67,12 @@ class GatewaySelectorBottomSheet : DSLSettingsBottomSheetFragment() { space(66.dp) + if (state.loading) { + customPref(IndeterminateLoadingCircle) + space(16.dp) + return@configure + } + if (state.isGooglePayAvailable) { customPref( GooglePayButton.Model( 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 new file mode 100644 index 0000000000..d60ee2bacf --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/subscription/donate/gateway/GatewaySelectorRepository.kt @@ -0,0 +1,25 @@ +package org.thoughtcrime.securesms.components.settings.app.subscription.donate.gateway + +import io.reactivex.rxjava3.core.Single +import org.thoughtcrime.securesms.components.settings.app.subscription.getAvailablePaymentMethods +import org.whispersystems.signalservice.api.services.DonationsService +import java.util.Locale + +class GatewaySelectorRepository( + private val donationsService: DonationsService +) { + fun getAvailableGateways(currencyCode: String): Single> { + return Single.fromCallable { + donationsService.getDonationsConfiguration(Locale.getDefault()) + }.flatMap { it.flattenResult() } + .map { configuration -> + configuration.getAvailablePaymentMethods(currencyCode).map { + when (it) { + "PAYPAL" -> listOf(GatewayResponse.Gateway.PAYPAL) + "CARD" -> listOf(GatewayResponse.Gateway.CREDIT_CARD, GatewayResponse.Gateway.GOOGLE_PAY) + else -> listOf() + } + }.flatten().toSet() + } + } +} 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 96c50b5879..ba5a8d8098 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 @@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.components.settings.app.subscription.donate.g import org.thoughtcrime.securesms.badges.models.Badge data class GatewaySelectorState( + val loading: Boolean = true, val badge: Badge, val isGooglePayAvailable: Boolean = false, val isPayPalAvailable: Boolean = false, 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 e7147ac0b0..0ef1a0fc6b 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 @@ -2,18 +2,21 @@ package org.thoughtcrime.securesms.components.settings.app.subscription.donate.g import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider +import io.reactivex.rxjava3.core.Single import io.reactivex.rxjava3.disposables.CompositeDisposable import io.reactivex.rxjava3.kotlin.plusAssign import io.reactivex.rxjava3.kotlin.subscribeBy import org.signal.donations.PaymentSourceType import org.thoughtcrime.securesms.components.settings.app.subscription.InAppDonations import org.thoughtcrime.securesms.components.settings.app.subscription.StripeRepository +import org.thoughtcrime.securesms.dependencies.ApplicationDependencies import org.thoughtcrime.securesms.keyvalue.SignalStore import org.thoughtcrime.securesms.util.rx.RxStore class GatewaySelectorViewModel( args: GatewaySelectorBottomSheetArgs, - private val repository: StripeRepository + repository: StripeRepository, + gatewaySelectorRepository: GatewaySelectorRepository ) : ViewModel() { private val store = RxStore( @@ -29,7 +32,19 @@ class GatewaySelectorViewModel( val state = store.stateFlowable init { - checkIfGooglePayIsAvailable() + 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) -> + 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) + ) + } + } } override fun onCleared() { @@ -37,25 +52,13 @@ class GatewaySelectorViewModel( disposables.clear() } - private fun checkIfGooglePayIsAvailable() { - disposables += repository.isGooglePayAvailable().subscribeBy( - onComplete = { - SignalStore.donationsValues().isGooglePayReady = true - store.update { it.copy(isGooglePayAvailable = true) } - }, - onError = { - SignalStore.donationsValues().isGooglePayReady = false - store.update { it.copy(isGooglePayAvailable = false) } - } - ) - } - class Factory( private val args: GatewaySelectorBottomSheetArgs, - private val repository: StripeRepository + private val repository: StripeRepository, + private val gatewaySelectorRepository: GatewaySelectorRepository = GatewaySelectorRepository(ApplicationDependencies.getDonationsService()) ) : ViewModelProvider.Factory { override fun create(modelClass: Class): T { - return modelClass.cast(GatewaySelectorViewModel(args, repository)) as T + return modelClass.cast(GatewaySelectorViewModel(args, repository, gatewaySelectorRepository)) as T } } } diff --git a/app/src/test/java/org/thoughtcrime/securesms/components/settings/app/subscription/DonationsConfigurationExtensionsKtTest.kt b/app/src/test/java/org/thoughtcrime/securesms/components/settings/app/subscription/DonationsConfigurationExtensionsKtTest.kt index 08bbe37f38..1c73e418b2 100644 --- a/app/src/test/java/org/thoughtcrime/securesms/components/settings/app/subscription/DonationsConfigurationExtensionsKtTest.kt +++ b/app/src/test/java/org/thoughtcrime/securesms/components/settings/app/subscription/DonationsConfigurationExtensionsKtTest.kt @@ -24,11 +24,11 @@ class DonationsConfigurationExtensionsKtTest { private val testSubject = JsonUtil.fromJson(testData, DonationsConfiguration::class.java) @Test - fun `Given all methods are available, when I getSubscriptionAmounts, then I expect BIF`() { + fun `Given all methods are available, when I getSubscriptionAmounts, then I expect all currencies`() { val subscriptionPrices = testSubject.getSubscriptionAmounts(DonationsConfiguration.SUBSCRIPTION_LEVELS.first(), AllPaymentMethodsAvailability) - assertEquals(1, subscriptionPrices.size) - assertEquals("BIF", subscriptionPrices.first().currency.currencyCode) + assertEquals(3, subscriptionPrices.size) + assertTrue(subscriptionPrices.map { it.currency.currencyCode }.containsAll(setOf("JPY", "BIF", "USD"))) } @Test @@ -84,11 +84,13 @@ class DonationsConfigurationExtensionsKtTest { } @Test - fun `Given all methods are available, when I getGiftAmounts, then I expect BIF`() { + fun `Given all methods are available, when I getGiftAmounts, then I expect BIF and JPY and USD`() { val giftAmounts = testSubject.getGiftBadgeAmounts(AllPaymentMethodsAvailability) - assertEquals(1, giftAmounts.size) + assertEquals(3, giftAmounts.size) assertNotNull(giftAmounts[Currency.getInstance("BIF")]) + assertNotNull(giftAmounts[Currency.getInstance("JPY")]) + assertNotNull(giftAmounts[Currency.getInstance("USD")]) } @Test @@ -108,11 +110,13 @@ class DonationsConfigurationExtensionsKtTest { } @Test - fun `Given all methods are available, when I getBoostAmounts, then I expect BIF`() { + fun `Given all methods are available, when I getBoostAmounts, then I expect BIF and JPY and USD`() { val boostAmounts = testSubject.getBoostAmounts(AllPaymentMethodsAvailability) - assertEquals(1, boostAmounts.size) + assertEquals(3, boostAmounts.size) assertNotNull(boostAmounts[Currency.getInstance("BIF")]) + assertNotNull(boostAmounts[Currency.getInstance("JPY")]) + assertNotNull(boostAmounts[Currency.getInstance("USD")]) } @Test @@ -132,11 +136,13 @@ class DonationsConfigurationExtensionsKtTest { } @Test - fun `Given all methods are available, when I getMinimumDonationAmounts, then I expect BIF`() { + fun `Given all methods are available, when I getMinimumDonationAmounts, then I expect BIF and JPY and USD`() { val minimumDonationAmounts = testSubject.getMinimumDonationAmounts(AllPaymentMethodsAvailability) - assertEquals(1, minimumDonationAmounts.size) + assertEquals(3, minimumDonationAmounts.size) assertNotNull(minimumDonationAmounts[Currency.getInstance("BIF")]) + assertNotNull(minimumDonationAmounts[Currency.getInstance("JPY")]) + assertNotNull(minimumDonationAmounts[Currency.getInstance("USD")]) } @Test @@ -185,6 +191,24 @@ class DonationsConfigurationExtensionsKtTest { } } + @Test + fun `Given I want to pay in USD, when I getAvailablePaymentMethods, then I expect CARD`() { + val availablePaymentMethods = testSubject.getAvailablePaymentMethods("UsD") + + assertEquals(1, availablePaymentMethods.size) + assertTrue("CARD" in availablePaymentMethods) + } + + @Test + fun `Given I want to pay in BIF, when I getAvailablePaymentMethods, then I expect CARD and PAYPAL`() { + val availablePaymentMethods = testSubject.getAvailablePaymentMethods("bIF") + + println(testSubject.currencies) + assertEquals(2, availablePaymentMethods.size) + assertTrue("CARD" in availablePaymentMethods) + assertTrue("PAYPAL" in availablePaymentMethods) + } + private object AllPaymentMethodsAvailability : PaymentMethodAvailability { override fun isPayPalAvailable(): Boolean = true override fun isGooglePayOrCreditCardAvailable(): Boolean = true diff --git a/app/src/test/resources/donations_configuration_test_data.json b/app/src/test/resources/donations_configuration_test_data.json index 1a4b536e00..f0375a43de 100644 --- a/app/src/test/resources/donations_configuration_test_data.json +++ b/app/src/test/resources/donations_configuration_test_data.json @@ -1,6 +1,6 @@ { "currencies": { - "JPY": { + "jpy": { "minimum": 300, "oneTime": { "1": [ @@ -24,7 +24,7 @@ "PAYPAL" ] }, - "USD": { + "usd": { "minimum": 2.5, "oneTime": { "1": [ @@ -48,7 +48,7 @@ "CARD" ] }, - "BIF": { + "bif": { "minimum": 3000, "oneTime": { "1": [