Implement refactor to utilize new donation configuration endpoint.

This commit is contained in:
Alex Hart
2022-12-06 12:07:24 -04:00
committed by Cody Henthorne
parent 40cf87307a
commit 424a0233c2
23 changed files with 847 additions and 229 deletions

View File

@@ -16,6 +16,7 @@
*/
package org.thoughtcrime.securesms;
import android.annotation.SuppressLint;
import android.content.Context;
import android.os.Build;
@@ -35,6 +36,8 @@ import org.signal.core.util.concurrent.SignalExecutors;
import org.signal.core.util.logging.AndroidLogger;
import org.signal.core.util.logging.Log;
import org.signal.core.util.tracing.Tracer;
import org.signal.donations.GooglePayApi;
import org.signal.donations.StripeApi;
import org.signal.glide.SignalGlideCodecs;
import org.signal.libsignal.protocol.logging.SignalProtocolLoggerProvider;
import org.signal.ringrtc.CallManager;
@@ -89,6 +92,7 @@ import org.thoughtcrime.securesms.storage.StorageSyncHelper;
import org.thoughtcrime.securesms.util.AppForegroundObserver;
import org.thoughtcrime.securesms.util.AppStartup;
import org.thoughtcrime.securesms.util.DynamicTheme;
import org.thoughtcrime.securesms.util.Environment;
import org.thoughtcrime.securesms.util.FeatureFlags;
import org.thoughtcrime.securesms.util.SignalLocalMetrics;
import org.thoughtcrime.securesms.util.SignalUncaughtExceptionHandler;
@@ -102,6 +106,8 @@ import java.net.SocketTimeoutException;
import java.security.Security;
import java.util.concurrent.TimeUnit;
import io.reactivex.rxjava3.core.CompletableObserver;
import io.reactivex.rxjava3.disposables.Disposable;
import io.reactivex.rxjava3.exceptions.OnErrorNotImplementedException;
import io.reactivex.rxjava3.exceptions.UndeliverableException;
import io.reactivex.rxjava3.plugins.RxJavaPlugins;
@@ -176,6 +182,7 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr
.addBlocking("blob-provider", this::initializeBlobProvider)
.addBlocking("feature-flags", FeatureFlags::init)
.addBlocking("glide", () -> SignalGlideModule.setRegisterGlideComponents(new SignalGlideComponents()))
.addNonBlocking(this::checkIsGooglePayReady)
.addNonBlocking(this::cleanAvatarStorage)
.addNonBlocking(this::initializeRevealableMessageManager)
.addNonBlocking(this::initializePendingRetryReceiptManager)
@@ -459,6 +466,18 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr
AvatarPickerStorage.cleanOrphans(this);
}
@SuppressLint("CheckResult")
private void checkIsGooglePayReady() {
GooglePayApi.queryIsReadyToPay(
this,
new StripeApi.Gateway(Environment.Donations.getStripeConfiguration()),
Environment.Donations.getGooglePayConfiguration()
).subscribe(
/* onComplete = */ () -> SignalStore.donationsValues().setGooglePayReady(true),
/* onError = */ t -> SignalStore.donationsValues().setGooglePayReady(false)
);
}
@WorkerThread
private void initializeCleanup() {
int deleted = SignalDatabase.attachments().deleteAbandonedPreuploadedAttachments();

View File

@@ -5,17 +5,17 @@ import io.reactivex.rxjava3.core.Single
import io.reactivex.rxjava3.schedulers.Schedulers
import org.signal.core.util.logging.Log
import org.signal.core.util.money.FiatMoney
import org.thoughtcrime.securesms.badges.Badges
import org.thoughtcrime.securesms.badges.models.Badge
import org.thoughtcrime.securesms.components.settings.app.subscription.errors.DonationError
import org.thoughtcrime.securesms.components.settings.app.subscription.getGiftBadgeAmounts
import org.thoughtcrime.securesms.components.settings.app.subscription.getGiftBadges
import org.thoughtcrime.securesms.database.RecipientTable
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.util.PlatformCurrencyUtil
import org.thoughtcrime.securesms.util.ProfileUtil
import org.whispersystems.signalservice.api.profiles.SignalServiceProfile
import org.whispersystems.signalservice.internal.ServiceResponse
import org.whispersystems.signalservice.internal.push.DonationsConfiguration
import java.io.IOException
import java.util.Currency
import java.util.Locale
@@ -29,15 +29,14 @@ class GiftFlowRepository {
private val TAG = Log.tag(GiftFlowRepository::class.java)
}
fun getGiftBadge(): Single<Pair<Long, Badge>> {
fun getGiftBadge(): Single<Pair<Int, Badge>> {
return Single
.fromCallable {
ApplicationDependencies.getDonationsService()
.getGiftBadges(Locale.getDefault())
.getDonationsConfiguration(Locale.getDefault())
}
.flatMap(ServiceResponse<Map<Long, SignalServiceProfile.Badge>>::flattenResult)
.map { gifts -> gifts.map { it.key to Badges.fromServiceBadge(it.value) } }
.map { it.first() }
.flatMap { it.flattenResult() }
.map { DonationsConfiguration.GIFT_LEVEL to it.getGiftBadges().first() }
.subscribeOn(Schedulers.io())
}
@@ -45,20 +44,17 @@ class GiftFlowRepository {
return Single
.fromCallable {
ApplicationDependencies.getDonationsService()
.giftAmount
.getDonationsConfiguration(Locale.getDefault())
}
.subscribeOn(Schedulers.io())
.flatMap { it.flattenResult() }
.map { result ->
result
.filter { PlatformCurrencyUtil.getAvailableCurrencyCodes().contains(it.key) }
.mapKeys { (code, _) -> Currency.getInstance(code) }
.mapValues { (currency, price) -> FiatMoney(price, currency) }
}
.map { it.getGiftBadgeAmounts() }
}
/**
* Verifies that the given recipient is a supported target for a gift.
*
* TODO[alex] - this needs to be incorporated into the correct flows.
*/
fun verifyRecipientIsAllowedToReceiveAGift(badgeRecipient: RecipientId): Completable {
return Completable.fromAction {

View File

@@ -83,7 +83,7 @@ class GiftFlowViewModel(
onSuccess = { (giftLevel, giftBadge) ->
store.update {
it.copy(
giftLevel = giftLevel,
giftLevel = giftLevel.toLong(),
giftBadge = giftBadge,
stage = getLoadState(it, giftBadge = giftBadge)
)

View File

@@ -4,8 +4,8 @@ import io.reactivex.rxjava3.core.Observable
import io.reactivex.rxjava3.core.Single
import io.reactivex.rxjava3.schedulers.Schedulers
import org.signal.libsignal.zkgroup.receipts.ReceiptCredentialPresentation
import org.thoughtcrime.securesms.badges.Badges
import org.thoughtcrime.securesms.badges.models.Badge
import org.thoughtcrime.securesms.components.settings.app.subscription.getBadge
import org.thoughtcrime.securesms.database.DatabaseObserver
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.database.model.MmsMessageRecord
@@ -23,10 +23,10 @@ class ViewGiftRepository {
.fromCallable {
ApplicationDependencies
.getDonationsService()
.getGiftBadge(Locale.getDefault(), presentation.receiptLevel)
.getDonationsConfiguration(Locale.getDefault())
}
.flatMap { it.flattenResult() }
.map { Badges.fromServiceBadge(it) }
.map { it.getBadge(presentation.receiptLevel.toInt()) }
.subscribeOn(Schedulers.io())
}

View File

@@ -11,6 +11,9 @@ import org.signal.donations.StripeDeclineCode
import org.thoughtcrime.securesms.badges.Badges
import org.thoughtcrime.securesms.badges.models.Badge
import org.thoughtcrime.securesms.components.settings.app.subscription.errors.UnexpectedSubscriptionCancellation
import org.thoughtcrime.securesms.components.settings.app.subscription.getBoostBadges
import org.thoughtcrime.securesms.components.settings.app.subscription.getGiftBadges
import org.thoughtcrime.securesms.components.settings.app.subscription.getSubscriptionLevels
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.jobs.SubscriptionReceiptRequestResponseJob
import org.thoughtcrime.securesms.keyvalue.SignalStore
@@ -29,28 +32,28 @@ class InternalDonorErrorConfigurationViewModel : ViewModel() {
val giftBadges: Single<List<Badge>> = Single
.fromCallable {
ApplicationDependencies.getDonationsService()
.getGiftBadges(Locale.getDefault())
.getDonationsConfiguration(Locale.getDefault())
}
.flatMap { it.flattenResult() }
.map { results -> results.values.map { Badges.fromServiceBadge(it) } }
.map { it.getGiftBadges() }
.subscribeOn(Schedulers.io())
val boostBadges: Single<List<Badge>> = Single
.fromCallable {
ApplicationDependencies.getDonationsService()
.getBoostBadge(Locale.getDefault())
.getDonationsConfiguration(Locale.getDefault())
}
.flatMap { it.flattenResult() }
.map { listOf(Badges.fromServiceBadge(it)) }
.map { it.getBoostBadges() }
.subscribeOn(Schedulers.io())
val subscriptionBadges: Single<List<Badge>> = Single
.fromCallable {
ApplicationDependencies.getDonationsService()
.getSubscriptionLevels(Locale.getDefault())
.getDonationsConfiguration(Locale.getDefault())
}
.flatMap { it.flattenResult() }
.map { levels -> levels.levels.values.map { Badges.fromServiceBadge(it.badge) } }
.map { config -> config.getSubscriptionLevels().values.map { Badges.fromServiceBadge(it.badge) } }
.subscribeOn(Schedulers.io())
disposables += Single.zip(giftBadges, boostBadges, subscriptionBadges) { g, b, s ->

View File

@@ -0,0 +1,123 @@
package org.thoughtcrime.securesms.components.settings.app.subscription
import org.signal.core.util.money.FiatMoney
import org.signal.core.util.money.PlatformCurrencyUtil
import org.thoughtcrime.securesms.badges.Badges
import org.thoughtcrime.securesms.badges.models.Badge
import org.whispersystems.signalservice.internal.push.DonationsConfiguration
import org.whispersystems.signalservice.internal.push.DonationsConfiguration.BOOST_LEVEL
import org.whispersystems.signalservice.internal.push.DonationsConfiguration.GIFT_LEVEL
import org.whispersystems.signalservice.internal.push.DonationsConfiguration.LevelConfiguration
import org.whispersystems.signalservice.internal.push.DonationsConfiguration.SUBSCRIPTION_LEVELS
import java.math.BigDecimal
import java.util.Currency
private const val CARD = "CARD"
private const val PAYPAL = "PAYPAL"
/**
* Transforms the DonationsConfiguration into a Set<FiatMoney> which has been properly filtered
* for available currencies on the platform and based off user device availability.
*
* CARD - Google Pay & Credit Card
* PAYPAL - PayPal
*
* @param level The subscription level to get amounts for
* @param paymentMethodAvailability Predicate object which checks whether different payment methods are availble.
*/
fun DonationsConfiguration.getSubscriptionAmounts(
level: Int,
paymentMethodAvailability: PaymentMethodAvailability = DefaultPaymentMethodAvailability
): Set<FiatMoney> {
require(SUBSCRIPTION_LEVELS.contains(level))
return getFilteredCurrencies(paymentMethodAvailability).map { (code, config) ->
val amount: BigDecimal = config.subscription[level]!!
FiatMoney(amount, Currency.getInstance(code.uppercase()))
}.toSet()
}
/**
* Currently, we only support a single gift badge at level GIFT_LEVEL
*/
fun DonationsConfiguration.getGiftBadges(): List<Badge> {
val configuration = levels[GIFT_LEVEL]
return listOfNotNull(configuration?.badge?.let { Badges.fromServiceBadge(it) })
}
/**
* Currently, we only support a single gift badge amount per currency
*/
fun DonationsConfiguration.getGiftBadgeAmounts(paymentMethodAvailability: PaymentMethodAvailability = DefaultPaymentMethodAvailability): Map<Currency, FiatMoney> {
return getFilteredCurrencies(paymentMethodAvailability).filter {
it.value.oneTime[GIFT_LEVEL]?.isNotEmpty() == true
}.mapKeys {
Currency.getInstance(it.key.uppercase())
}.mapValues {
FiatMoney(it.value.oneTime[GIFT_LEVEL]!!.first(), it.key)
}
}
/**
* Currently, we only support a single boost badge at level BOOST_LEVEL
*/
fun DonationsConfiguration.getBoostBadges(): List<Badge> {
val configuration = levels[BOOST_LEVEL]
return listOfNotNull(configuration?.badge?.let { Badges.fromServiceBadge(it) })
}
fun DonationsConfiguration.getBoostAmounts(paymentMethodAvailability: PaymentMethodAvailability = DefaultPaymentMethodAvailability): Map<Currency, List<FiatMoney>> {
return getFilteredCurrencies(paymentMethodAvailability).filter {
it.value.oneTime[BOOST_LEVEL]?.isNotEmpty() == true
}.mapKeys {
Currency.getInstance(it.key.uppercase())
}.mapValues { (currency, config) ->
config.oneTime[BOOST_LEVEL]!!.map { FiatMoney(it, currency) }
}
}
fun DonationsConfiguration.getBadge(level: Int): Badge {
require(level == GIFT_LEVEL || level == BOOST_LEVEL || SUBSCRIPTION_LEVELS.contains(level))
return Badges.fromServiceBadge(levels[level]!!.badge)
}
fun DonationsConfiguration.getSubscriptionLevels(): Map<Int, LevelConfiguration> {
return levels.filterKeys { SUBSCRIPTION_LEVELS.contains(it) }.toSortedMap()
}
private fun DonationsConfiguration.getFilteredCurrencies(paymentMethodAvailability: PaymentMethodAvailability): Map<String, DonationsConfiguration.CurrencyConfiguration> {
val userPaymentMethods = paymentMethodAvailability.toSet()
val availableCurrencyCodes = PlatformCurrencyUtil.getAvailableCurrencyCodes()
return currencies.filter { (code, config) ->
val areAllMethodsAvailable = config.supportedPaymentMethods.containsAll(userPaymentMethods)
availableCurrencyCodes.contains(code.uppercase()) && areAllMethodsAvailable
}
}
/**
* This interface is available to ease unit testing of the extension methods in
* this file. In all normal situations, you can just allow the methods to use the
* default value.
*/
interface PaymentMethodAvailability {
fun isPayPalAvailable(): Boolean
fun isGooglePayOrCreditCardAvailable(): Boolean
fun toSet(): Set<String> {
val set = mutableSetOf<String>()
if (isPayPalAvailable()) {
set.add(PAYPAL)
}
if (isGooglePayOrCreditCardAvailable()) {
set.add(CARD)
}
return set
}
}
private object DefaultPaymentMethodAvailability : PaymentMethodAvailability {
override fun isPayPalAvailable(): Boolean = InAppDonations.isPayPalAvailable()
override fun isGooglePayOrCreditCardAvailable(): Boolean = InAppDonations.isCreditCardAvailable() || InAppDonations.isGooglePayAvailable()
}

View File

@@ -2,10 +2,9 @@ package org.thoughtcrime.securesms.components.settings.app.subscription
import org.signal.donations.PaymentSourceType
import org.thoughtcrime.securesms.components.settings.app.subscription.donate.DonateToSignalType
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.util.FeatureFlags
import org.thoughtcrime.securesms.util.LocaleFeatureFlags
import org.thoughtcrime.securesms.util.PlayServicesUtil
/**
* Helper object to determine in-app donations availability.
@@ -42,29 +41,21 @@ object InAppDonations {
/**
* Whether the user is in a region that supports credit cards, based off local phone number.
*/
private fun isCreditCardAvailable(): Boolean {
fun isCreditCardAvailable(): Boolean {
return FeatureFlags.creditCardPayments() && !LocaleFeatureFlags.isCreditCardDisabled()
}
/**
* Whether the user is in a region that supports PayPal, based off local phone number.
*/
private fun isPayPalAvailable(): Boolean {
fun isPayPalAvailable(): Boolean {
return (FeatureFlags.paypalOneTimeDonations() || FeatureFlags.paypalRecurringDonations()) && !LocaleFeatureFlags.isPayPalDisabled()
}
/**
* Whether the user is in a region that supports GooglePay, based off local phone number.
* Whether the user is using a device that supports GooglePay, based off Wallet API and phone number.
*/
private fun isGooglePayAvailable(): Boolean {
return isPlayServicesAvailable() && !LocaleFeatureFlags.isGooglePayDisabled()
}
/**
* Whether Play Services is available. This will *not* tell you whether a user has Google Pay set up, but is
* enough information to determine whether we can display Google Pay as an option.
*/
private fun isPlayServicesAvailable(): Boolean {
return PlayServicesUtil.getPlayServicesStatus(ApplicationDependencies.getApplication()) == PlayServicesUtil.PlayServicesStatus.SUCCESS
fun isGooglePayAvailable(): Boolean {
return SignalStore.donationsValues().isGooglePayReady && !LocaleFeatureFlags.isGooglePayDisabled()
}
}

View File

@@ -4,7 +4,6 @@ import io.reactivex.rxjava3.core.Completable
import io.reactivex.rxjava3.core.Single
import io.reactivex.rxjava3.schedulers.Schedulers
import org.signal.core.util.logging.Log
import org.signal.core.util.money.FiatMoney
import org.thoughtcrime.securesms.badges.Badges
import org.thoughtcrime.securesms.components.settings.app.subscription.errors.DonationError
import org.thoughtcrime.securesms.components.settings.app.subscription.errors.DonationErrorSource
@@ -20,15 +19,12 @@ import org.thoughtcrime.securesms.subscription.LevelUpdate
import org.thoughtcrime.securesms.subscription.LevelUpdateOperation
import org.thoughtcrime.securesms.subscription.Subscriber
import org.thoughtcrime.securesms.subscription.Subscription
import org.thoughtcrime.securesms.util.PlatformCurrencyUtil
import org.whispersystems.signalservice.api.services.DonationsService
import org.whispersystems.signalservice.api.subscriptions.ActiveSubscription
import org.whispersystems.signalservice.api.subscriptions.IdempotencyKey
import org.whispersystems.signalservice.api.subscriptions.SubscriberId
import org.whispersystems.signalservice.api.subscriptions.SubscriptionLevels
import org.whispersystems.signalservice.internal.EmptyResponse
import org.whispersystems.signalservice.internal.ServiceResponse
import java.util.Currency
import java.util.Locale
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
@@ -52,29 +48,23 @@ class MonthlyDonationRepository(private val donationsService: DonationsService)
}
}
fun getSubscriptions(): Single<List<Subscription>> = Single
.fromCallable { donationsService.getSubscriptionLevels(Locale.getDefault()) }
.subscribeOn(Schedulers.io())
.flatMap(ServiceResponse<SubscriptionLevels>::flattenResult)
.map { subscriptionLevels ->
subscriptionLevels.levels.map { (code, level) ->
Subscription(
id = code,
name = level.name,
badge = Badges.fromServiceBadge(level.badge),
prices = level.currencies.filter {
PlatformCurrencyUtil
.getAvailableCurrencyCodes()
.contains(it.key)
}.map { (currencyCode, price) ->
FiatMoney(price, Currency.getInstance(currencyCode))
}.toSet(),
level = code.toInt()
)
}.sortedBy {
it.level
fun getSubscriptions(): Single<List<Subscription>> {
return Single
.fromCallable { donationsService.getDonationsConfiguration(Locale.getDefault()) }
.subscribeOn(Schedulers.io())
.flatMap { it.flattenResult() }
.map { config ->
config.getSubscriptionLevels().map { (level, levelConfig) ->
Subscription(
id = level.toString(),
level = level,
name = levelConfig.name,
badge = Badges.fromServiceBadge(levelConfig.badge),
prices = config.getSubscriptionAmounts(level)
)
}
}
}
}
fun syncAccountRecord(): Completable {
return Completable.fromAction {

View File

@@ -6,7 +6,6 @@ import io.reactivex.rxjava3.schedulers.Schedulers
import org.signal.core.util.logging.Log
import org.signal.core.util.money.FiatMoney
import org.signal.donations.PaymentSourceType
import org.thoughtcrime.securesms.badges.Badges
import org.thoughtcrime.securesms.badges.models.Badge
import org.thoughtcrime.securesms.components.settings.app.subscription.boost.Boost
import org.thoughtcrime.securesms.components.settings.app.subscription.errors.DonationError
@@ -18,12 +17,8 @@ import org.thoughtcrime.securesms.jobmanager.JobTracker
import org.thoughtcrime.securesms.jobs.BoostReceiptRequestResponseJob
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.util.PlatformCurrencyUtil
import org.whispersystems.signalservice.api.profiles.SignalServiceProfile
import org.whispersystems.signalservice.api.services.DonationsService
import org.whispersystems.signalservice.internal.ServiceResponse
import org.whispersystems.signalservice.internal.push.DonationProcessor
import java.math.BigDecimal
import java.util.Currency
import java.util.Locale
import java.util.concurrent.CountDownLatch
@@ -46,14 +41,15 @@ class OneTimeDonationRepository(private val donationsService: DonationsService)
}
fun getBoosts(): Single<Map<Currency, List<Boost>>> {
return Single.fromCallable { donationsService.boostAmounts }
return Single.fromCallable { donationsService.getDonationsConfiguration(Locale.getDefault()) }
.subscribeOn(Schedulers.io())
.flatMap(ServiceResponse<Map<String, List<BigDecimal>>>::flattenResult)
.map { result ->
result
.filter { PlatformCurrencyUtil.getAvailableCurrencyCodes().contains(it.key) }
.mapKeys { (code, _) -> Currency.getInstance(code) }
.mapValues { (currency, prices) -> prices.map { Boost(FiatMoney(it, currency)) } }
.flatMap { it.flattenResult() }
.map { config ->
config.getBoostAmounts().mapValues { (_, value) ->
value.map {
Boost(it)
}
}
}
}
@@ -61,11 +57,11 @@ class OneTimeDonationRepository(private val donationsService: DonationsService)
return Single
.fromCallable {
ApplicationDependencies.getDonationsService()
.getBoostBadge(Locale.getDefault())
.getDonationsConfiguration(Locale.getDefault())
}
.subscribeOn(Schedulers.io())
.flatMap(ServiceResponse<SignalServiceProfile.Badge>::flattenResult)
.map(Badges::fromServiceBadge)
.flatMap { it.flattenResult() }
.map { it.getBoostBadges().first() }
}
fun waitForOneTimeRedemption(

View File

@@ -12,6 +12,7 @@ import io.reactivex.rxjava3.subjects.PublishSubject
import org.signal.core.util.StringUtil
import org.signal.core.util.logging.Log
import org.signal.core.util.money.FiatMoney
import org.signal.core.util.money.PlatformCurrencyUtil
import org.thoughtcrime.securesms.components.settings.app.subscription.MonthlyDonationRepository
import org.thoughtcrime.securesms.components.settings.app.subscription.OneTimeDonationRepository
import org.thoughtcrime.securesms.components.settings.app.subscription.boost.Boost
@@ -25,7 +26,6 @@ import org.thoughtcrime.securesms.subscription.LevelUpdate
import org.thoughtcrime.securesms.subscription.Subscriber
import org.thoughtcrime.securesms.subscription.Subscription
import org.thoughtcrime.securesms.util.InternetConnectionObserver
import org.thoughtcrime.securesms.util.PlatformCurrencyUtil
import org.thoughtcrime.securesms.util.rx.RxStore
import org.whispersystems.signalservice.api.subscriptions.ActiveSubscription
import org.whispersystems.signalservice.api.subscriptions.SubscriberId

View File

@@ -8,6 +8,7 @@ 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.keyvalue.SignalStore
import org.thoughtcrime.securesms.util.rx.RxStore
class GatewaySelectorViewModel(
@@ -39,9 +40,11 @@ class GatewaySelectorViewModel(
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) }
}
)

View File

@@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.components.settings.app.subscription.receipts
import io.reactivex.rxjava3.core.Single
import io.reactivex.rxjava3.schedulers.Schedulers
import org.thoughtcrime.securesms.components.settings.app.subscription.getSubscriptionLevels
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.database.model.DonationReceiptRecord
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
@@ -13,10 +14,10 @@ class DonationReceiptDetailRepository {
.fromCallable {
ApplicationDependencies
.getDonationsService()
.getSubscriptionLevels(Locale.getDefault())
.getDonationsConfiguration(Locale.getDefault())
}
.flatMap { it.flattenResult() }
.map { it.levels[subscriptionLevel.toString()] ?: throw Exception("Subscription level $subscriptionLevel not found") }
.map { it.getSubscriptionLevels()[subscriptionLevel] ?: throw Exception("Subscription level $subscriptionLevel not found") }
.map { it.name }
.subscribeOn(Schedulers.io())
}

View File

@@ -3,6 +3,8 @@ package org.thoughtcrime.securesms.components.settings.app.subscription.receipts
import io.reactivex.rxjava3.core.Single
import io.reactivex.rxjava3.schedulers.Schedulers
import org.thoughtcrime.securesms.badges.Badges
import org.thoughtcrime.securesms.components.settings.app.subscription.getBoostBadges
import org.thoughtcrime.securesms.components.settings.app.subscription.getSubscriptionLevels
import org.thoughtcrime.securesms.database.model.DonationReceiptRecord
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import java.util.Locale
@@ -12,23 +14,23 @@ class DonationReceiptListRepository {
val boostBadges: Single<List<DonationReceiptBadge>> = Single
.fromCallable {
ApplicationDependencies.getDonationsService()
.getBoostBadge(Locale.getDefault())
.getDonationsConfiguration(Locale.getDefault())
}
.map { response ->
if (response.result.isPresent) {
listOf(DonationReceiptBadge(DonationReceiptRecord.Type.BOOST, -1, Badges.fromServiceBadge(response.result.get())))
listOf(DonationReceiptBadge(DonationReceiptRecord.Type.BOOST, -1, response.result.get().getBoostBadges().first()))
} else {
emptyList()
}
}
val subBadges: Single<List<DonationReceiptBadge>> = Single
.fromCallable { ApplicationDependencies.getDonationsService().getSubscriptionLevels(Locale.getDefault()) }
.fromCallable { ApplicationDependencies.getDonationsService().getDonationsConfiguration(Locale.getDefault()) }
.map { response ->
if (response.result.isPresent) {
response.result.get().levels.map {
response.result.get().getSubscriptionLevels().map {
DonationReceiptBadge(
level = it.key.toInt(),
level = it.key,
badge = Badges.fromServiceBadge(it.value.badge),
type = DonationReceiptRecord.Type.RECURRING
)

View File

@@ -11,7 +11,7 @@ import com.bumptech.glide.load.model.ModelLoaderFactory
import com.bumptech.glide.load.model.MultiModelLoaderFactory
import okhttp3.OkHttpClient
import org.signal.libsignal.zkgroup.receipts.ReceiptCredentialPresentation
import org.thoughtcrime.securesms.badges.Badges
import org.thoughtcrime.securesms.components.settings.app.subscription.getBadge
import org.thoughtcrime.securesms.database.model.databaseprotos.GiftBadge
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import java.io.InputStream
@@ -47,9 +47,9 @@ data class GiftBadgeModel(val giftBadge: GiftBadge) : Key {
override fun loadData(priority: Priority, callback: DataFetcher.DataCallback<in InputStream>) {
try {
val receiptCredentialPresentation = ReceiptCredentialPresentation(giftBadge.giftBadge.redemptionToken.toByteArray())
val giftBadgeResponse = ApplicationDependencies.getDonationsService().getGiftBadge(Locale.getDefault(), receiptCredentialPresentation.receiptLevel)
val giftBadgeResponse = ApplicationDependencies.getDonationsService().getDonationsConfiguration(Locale.getDefault())
if (giftBadgeResponse.result.isPresent) {
val badge = Badges.fromServiceBadge(giftBadgeResponse.result.get())
val badge = giftBadgeResponse.result.get().getBadge(receiptCredentialPresentation.receiptLevel.toInt())
okHttpStreamFetcher = OkHttpStreamFetcher(client, GlideUrl(badge.imageUrl.toString()))
okHttpStreamFetcher?.loadData(priority, callback)
} else if (giftBadgeResponse.applicationError.isPresent) {

View File

@@ -102,6 +102,12 @@ internal class DonationsValues internal constructor(store: KeyValueStore) : Sign
* in determining which error messaging they should see if something goes wrong.
*/
private const val SUBSCRIPTION_PAYMENT_SOURCE_TYPE = "subscription.payment.source.type"
/**
* Marked whenever we check for Google Pay availability, to help make decisions without
* awaiting the background task.
*/
private const val IS_GOOGLE_PAY_READY = "subscription.is.google.pay.ready"
}
override fun onFirstEverAppLaunch() = Unit
@@ -354,6 +360,8 @@ internal class DonationsValues internal constructor(store: KeyValueStore) : Sign
get() = getBoolean(SHOULD_CANCEL_SUBSCRIPTION_BEFORE_NEXT_SUBSCRIBE_ATTEMPT, false)
set(value) = putBoolean(SHOULD_CANCEL_SUBSCRIPTION_BEFORE_NEXT_SUBSCRIBE_ATTEMPT, value)
var isGooglePayReady: Boolean by booleanValue(IS_GOOGLE_PAY_READY, false)
/**
* Consolidates a bunch of data clears that should occur whenever a user manually cancels their
* subscription:

View File

@@ -9,10 +9,14 @@ object Environment {
const val IS_STAGING: Boolean = BuildConfig.BUILD_ENVIRONMENT_TYPE == "Staging"
object Donations {
@JvmStatic
@get:JvmName("getGooglePayConfiguration")
val GOOGLE_PAY_CONFIGURATION = GooglePayApi.Configuration(
walletEnvironment = if (IS_STAGING) WalletConstants.ENVIRONMENT_TEST else WalletConstants.ENVIRONMENT_PRODUCTION
)
@JvmStatic
@get:JvmName("getStripeConfiguration")
val STRIPE_CONFIGURATION = StripeApi.Configuration(
publishableKey = BuildConfig.STRIPE_PUBLISHABLE_KEY
)

View File

@@ -1,24 +0,0 @@
package org.thoughtcrime.securesms.util
import java.util.Currency
/**
* Utility methods for java.util.Currency
*
* This is prefixed with "Platform" as there are several different Currency classes
* available in the app, and this utility class is specifically for dealing with
* java.util.Currency
*/
object PlatformCurrencyUtil {
val USD: Currency = Currency.getInstance("USD")
/**
* Note: Adding this as an extension method of Currency causes some confusion in
* AndroidStudio due to a separate Currency class from the AndroidSDK having
* an extension method of the same signature.
*/
fun getAvailableCurrencyCodes(): Set<String> {
return Currency.getAvailableCurrencies().map { it.currencyCode }.toSet()
}
}