Implement refactor to utilize new donation configuration endpoint.
This commit is contained in:
parent
40cf87307a
commit
424a0233c2
23 changed files with 847 additions and 229 deletions
|
@ -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();
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
)
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
|
||||
|
|
|
@ -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 ->
|
||||
|
|
|
@ -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()
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) }
|
||||
}
|
||||
)
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
|
|
@ -0,0 +1,178 @@
|
|||
package org.thoughtcrime.securesms.components.settings.app.subscription
|
||||
|
||||
import android.app.Application
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import io.mockk.every
|
||||
import io.mockk.mockkStatic
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import org.robolectric.annotation.Config
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||
import org.whispersystems.signalservice.internal.push.DonationsConfiguration
|
||||
import org.whispersystems.signalservice.internal.util.JsonUtil
|
||||
import java.util.Currency
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
@Config(application = Application::class)
|
||||
class DonationsConfigurationExtensionsKtTest {
|
||||
|
||||
private val testData: String = javaClass.classLoader!!.getResourceAsStream("donations_configuration_test_data.json").bufferedReader().readText()
|
||||
private val testSubject = JsonUtil.fromJson(testData, DonationsConfiguration::class.java)
|
||||
|
||||
@Test
|
||||
fun `Given all methods are available, when I getSubscriptionAmounts, then I expect BIF`() {
|
||||
val subscriptionPrices = testSubject.getSubscriptionAmounts(DonationsConfiguration.SUBSCRIPTION_LEVELS.first(), AllPaymentMethodsAvailability)
|
||||
|
||||
assertEquals(1, subscriptionPrices.size)
|
||||
assertEquals("BIF", subscriptionPrices.first().currency.currencyCode)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Given only PayPal available, when I getSubscriptionAmounts, then I expect BIF and JPY`() {
|
||||
val subscriptionPrices = testSubject.getSubscriptionAmounts(DonationsConfiguration.SUBSCRIPTION_LEVELS.first(), PayPalOnly)
|
||||
|
||||
assertEquals(2, subscriptionPrices.size)
|
||||
assertTrue(subscriptionPrices.map { it.currency.currencyCode }.containsAll(setOf("JPY", "BIF")))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Given only Card available, when I getSubscriptionAmounts, then I expect BIF and USD`() {
|
||||
val subscriptionPrices = testSubject.getSubscriptionAmounts(DonationsConfiguration.SUBSCRIPTION_LEVELS.first(), CardOnly)
|
||||
|
||||
assertEquals(2, subscriptionPrices.size)
|
||||
assertTrue(subscriptionPrices.map { it.currency.currencyCode }.containsAll(setOf("USD", "BIF")))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `When I getGiftBadges, then I expect exactly 1 badge with the id GIFT`() {
|
||||
mockkStatic(ApplicationDependencies::class) {
|
||||
every { ApplicationDependencies.getApplication() } returns ApplicationProvider.getApplicationContext()
|
||||
|
||||
val giftBadges = testSubject.getGiftBadges()
|
||||
|
||||
assertEquals(1, giftBadges.size)
|
||||
assertTrue(giftBadges.first().isGift())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `When I getBoostBadges, then I expect exactly 1 badge with the id BOOST`() {
|
||||
mockkStatic(ApplicationDependencies::class) {
|
||||
every { ApplicationDependencies.getApplication() } returns ApplicationProvider.getApplicationContext()
|
||||
|
||||
val boostBadges = testSubject.getBoostBadges()
|
||||
|
||||
assertEquals(1, boostBadges.size)
|
||||
assertTrue(boostBadges.first().isBoost())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `When I getSubscriptionLevels, then I expect the exact 3 defined subscription levels`() {
|
||||
val subscriptionLevels = testSubject.getSubscriptionLevels()
|
||||
|
||||
assertEquals(3, subscriptionLevels.size)
|
||||
assertEquals(DonationsConfiguration.SUBSCRIPTION_LEVELS, subscriptionLevels.keys)
|
||||
subscriptionLevels.keys.fold(0) { acc, i ->
|
||||
assertTrue(acc < i)
|
||||
i
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Given all methods are available, when I getGiftAmounts, then I expect BIF`() {
|
||||
val giftAmounts = testSubject.getGiftBadgeAmounts(AllPaymentMethodsAvailability)
|
||||
|
||||
assertEquals(1, giftAmounts.size)
|
||||
assertNotNull(giftAmounts[Currency.getInstance("BIF")])
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Given only PayPal available, when I getGiftAmounts, then I expect BIF and JPY`() {
|
||||
val giftAmounts = testSubject.getGiftBadgeAmounts(PayPalOnly)
|
||||
|
||||
assertEquals(2, giftAmounts.size)
|
||||
assertTrue(giftAmounts.map { it.key.currencyCode }.containsAll(setOf("JPY", "BIF")))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Given only Card available, when I getGiftAmounts, then I expect BIF and USD`() {
|
||||
val giftAmounts = testSubject.getGiftBadgeAmounts(CardOnly)
|
||||
|
||||
assertEquals(2, giftAmounts.size)
|
||||
assertTrue(giftAmounts.map { it.key.currencyCode }.containsAll(setOf("USD", "BIF")))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Given all methods are available, when I getBoostAmounts, then I expect BIF`() {
|
||||
val boostAmounts = testSubject.getBoostAmounts(AllPaymentMethodsAvailability)
|
||||
|
||||
assertEquals(1, boostAmounts.size)
|
||||
assertNotNull(boostAmounts[Currency.getInstance("BIF")])
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Given only PayPal available, when I getBoostAmounts, then I expect BIF and JPY`() {
|
||||
val boostAmounts = testSubject.getBoostAmounts(PayPalOnly)
|
||||
|
||||
assertEquals(2, boostAmounts.size)
|
||||
assertTrue(boostAmounts.map { it.key.currencyCode }.containsAll(setOf("JPY", "BIF")))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Given only Card available, when I getBoostAmounts, then I expect BIF and USD`() {
|
||||
val boostAmounts = testSubject.getBoostAmounts(CardOnly)
|
||||
|
||||
assertEquals(2, boostAmounts.size)
|
||||
assertTrue(boostAmounts.map { it.key.currencyCode }.containsAll(setOf("USD", "BIF")))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Given GIFT_LEVEL, When I getBadge, then I expect the gift badge`() {
|
||||
mockkStatic(ApplicationDependencies::class) {
|
||||
every { ApplicationDependencies.getApplication() } returns ApplicationProvider.getApplicationContext()
|
||||
val badge = testSubject.getBadge(DonationsConfiguration.GIFT_LEVEL)
|
||||
|
||||
assertTrue(badge.isGift())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Given BOOST_LEVEL, When I getBadge, then I expect the boost badge`() {
|
||||
mockkStatic(ApplicationDependencies::class) {
|
||||
every { ApplicationDependencies.getApplication() } returns ApplicationProvider.getApplicationContext()
|
||||
val badge = testSubject.getBadge(DonationsConfiguration.BOOST_LEVEL)
|
||||
|
||||
assertTrue(badge.isBoost())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Given a sub level, When I getBadge, then I expect a sub badge`() {
|
||||
mockkStatic(ApplicationDependencies::class) {
|
||||
every { ApplicationDependencies.getApplication() } returns ApplicationProvider.getApplicationContext()
|
||||
val badge = testSubject.getBadge(DonationsConfiguration.SUBSCRIPTION_LEVELS.first())
|
||||
|
||||
assertTrue(badge.isSubscription())
|
||||
}
|
||||
}
|
||||
|
||||
private object AllPaymentMethodsAvailability : PaymentMethodAvailability {
|
||||
override fun isPayPalAvailable(): Boolean = true
|
||||
override fun isGooglePayOrCreditCardAvailable(): Boolean = true
|
||||
}
|
||||
|
||||
private object PayPalOnly : PaymentMethodAvailability {
|
||||
override fun isPayPalAvailable(): Boolean = true
|
||||
override fun isGooglePayOrCreditCardAvailable(): Boolean = false
|
||||
}
|
||||
|
||||
private object CardOnly : PaymentMethodAvailability {
|
||||
override fun isPayPalAvailable(): Boolean = false
|
||||
override fun isGooglePayOrCreditCardAvailable(): Boolean = true
|
||||
}
|
||||
}
|
245
app/src/test/resources/donations_configuration_test_data.json
Normal file
245
app/src/test/resources/donations_configuration_test_data.json
Normal file
|
@ -0,0 +1,245 @@
|
|||
{
|
||||
"currencies": {
|
||||
"JPY": {
|
||||
"minimum": 300,
|
||||
"oneTime": {
|
||||
"1": [
|
||||
500,
|
||||
600,
|
||||
700,
|
||||
800,
|
||||
900,
|
||||
1000
|
||||
],
|
||||
"100": [
|
||||
3000
|
||||
]
|
||||
},
|
||||
"subscription": {
|
||||
"2000": 35000,
|
||||
"1000": 15000,
|
||||
"500": 5000
|
||||
},
|
||||
"supportedPaymentMethods": [
|
||||
"PAYPAL"
|
||||
]
|
||||
},
|
||||
"USD": {
|
||||
"minimum": 2.5,
|
||||
"oneTime": {
|
||||
"1": [
|
||||
5.5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
10
|
||||
],
|
||||
"100": [
|
||||
20
|
||||
]
|
||||
},
|
||||
"subscription": {
|
||||
"2000": 35,
|
||||
"1000": 15,
|
||||
"500": 5
|
||||
},
|
||||
"supportedPaymentMethods": [
|
||||
"CARD"
|
||||
]
|
||||
},
|
||||
"BIF": {
|
||||
"minimum": 3000,
|
||||
"oneTime": {
|
||||
"1": [
|
||||
5000,
|
||||
6000,
|
||||
7000,
|
||||
8000,
|
||||
9000,
|
||||
10000
|
||||
],
|
||||
"100": [
|
||||
50000
|
||||
]
|
||||
},
|
||||
"subscription": {
|
||||
"2000": 350000,
|
||||
"1000": 150000,
|
||||
"500": 50000
|
||||
},
|
||||
"supportedPaymentMethods": [
|
||||
"CARD", "PAYPAL"
|
||||
]
|
||||
}
|
||||
},
|
||||
"levels": {
|
||||
"1": {
|
||||
"name": "ZBOOST",
|
||||
"badge": {
|
||||
"id": "BOOST",
|
||||
"category": "boost1",
|
||||
"name": "boost1",
|
||||
"description": "boost1",
|
||||
"sprites6": [
|
||||
"l",
|
||||
"m",
|
||||
"h",
|
||||
"x",
|
||||
"xx",
|
||||
"xxx"
|
||||
],
|
||||
"svg": "SVG",
|
||||
"svgs": [
|
||||
{
|
||||
"light": "sl",
|
||||
"dark": "sd"
|
||||
},
|
||||
{
|
||||
"light": "ml",
|
||||
"dark": "md"
|
||||
},
|
||||
{
|
||||
"light": "ll",
|
||||
"dark": "ld"
|
||||
}
|
||||
],
|
||||
"duration": 2592000,
|
||||
"imageUrl": ""
|
||||
}
|
||||
},
|
||||
"100": {
|
||||
"name": "ZGIFT",
|
||||
"badge": {
|
||||
"id": "GIFT",
|
||||
"category": "gift1",
|
||||
"name": "gift1",
|
||||
"description": "gift1",
|
||||
"sprites6": [
|
||||
"l",
|
||||
"m",
|
||||
"h",
|
||||
"x",
|
||||
"xx",
|
||||
"xxx"
|
||||
],
|
||||
"svg": "SVG",
|
||||
"svgs": [
|
||||
{
|
||||
"light": "sl",
|
||||
"dark": "sd"
|
||||
},
|
||||
{
|
||||
"light": "ml",
|
||||
"dark": "md"
|
||||
},
|
||||
{
|
||||
"light": "ll",
|
||||
"dark": "ld"
|
||||
}
|
||||
],
|
||||
"duration": 5184000,
|
||||
"imageUrl": ""
|
||||
}
|
||||
},
|
||||
"2000": {
|
||||
"name": "Z3",
|
||||
"badge": {
|
||||
"id": "B3",
|
||||
"category": "cat3",
|
||||
"name": "name3",
|
||||
"description": "desc3",
|
||||
"sprites6": [
|
||||
"l",
|
||||
"m",
|
||||
"h",
|
||||
"x",
|
||||
"xx",
|
||||
"xxx"
|
||||
],
|
||||
"svg": "SVG",
|
||||
"svgs": [
|
||||
{
|
||||
"light": "sl",
|
||||
"dark": "sd"
|
||||
},
|
||||
{
|
||||
"light": "ml",
|
||||
"dark": "md"
|
||||
},
|
||||
{
|
||||
"light": "ll",
|
||||
"dark": "ld"
|
||||
}
|
||||
],
|
||||
"imageUrl": ""
|
||||
}
|
||||
},
|
||||
"1000": {
|
||||
"name": "Z2",
|
||||
"badge": {
|
||||
"id": "B2",
|
||||
"category": "cat2",
|
||||
"name": "name2",
|
||||
"description": "desc2",
|
||||
"sprites6": [
|
||||
"l",
|
||||
"m",
|
||||
"h",
|
||||
"x",
|
||||
"xx",
|
||||
"xxx"
|
||||
],
|
||||
"svg": "SVG",
|
||||
"svgs": [
|
||||
{
|
||||
"light": "sl",
|
||||
"dark": "sd"
|
||||
},
|
||||
{
|
||||
"light": "ml",
|
||||
"dark": "md"
|
||||
},
|
||||
{
|
||||
"light": "ll",
|
||||
"dark": "ld"
|
||||
}
|
||||
],
|
||||
"imageUrl": ""
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"name": "Z1",
|
||||
"badge": {
|
||||
"id": "B1",
|
||||
"category": "cat1",
|
||||
"name": "name1",
|
||||
"description": "desc1",
|
||||
"sprites6": [
|
||||
"l",
|
||||
"m",
|
||||
"h",
|
||||
"x",
|
||||
"xx",
|
||||
"xxx"
|
||||
],
|
||||
"svg": "SVG",
|
||||
"svgs": [
|
||||
{
|
||||
"light": "sl",
|
||||
"dark": "sd"
|
||||
},
|
||||
{
|
||||
"light": "ml",
|
||||
"dark": "md"
|
||||
},
|
||||
{
|
||||
"light": "ll",
|
||||
"dark": "ld"
|
||||
}
|
||||
],
|
||||
"imageUrl": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package org.thoughtcrime.securesms.util
|
||||
package org.signal.core.util.money
|
||||
|
||||
import java.util.Currency
|
||||
|
||||
|
@ -21,4 +21,4 @@ object PlatformCurrencyUtil {
|
|||
fun getAvailableCurrencyCodes(): Set<String> {
|
||||
return Currency.getAvailableCurrencies().map { it.currencyCode }.toSet()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
package org.signal.donations
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import com.google.android.gms.common.api.ApiException
|
||||
import com.google.android.gms.tasks.Task
|
||||
|
@ -28,7 +29,7 @@ import java.util.Locale
|
|||
class GooglePayApi(
|
||||
private val activity: Activity,
|
||||
private val gateway: Gateway,
|
||||
configuration: Configuration
|
||||
private val configuration: Configuration
|
||||
) {
|
||||
|
||||
private val paymentsClient: PaymentsClient
|
||||
|
@ -41,33 +42,9 @@ class GooglePayApi(
|
|||
paymentsClient = Wallet.getPaymentsClient(activity, walletOptions)
|
||||
}
|
||||
|
||||
/**
|
||||
* Query the Google Pay API to determine whether or not the device has Google Pay available and ready.
|
||||
*
|
||||
* @return A completable which, when it completes, indicates that Google Pay is available, or when it errors, indicates it is not.
|
||||
*/
|
||||
fun queryIsReadyToPay(): Completable = Completable.create { emitter ->
|
||||
try {
|
||||
val request: IsReadyToPayRequest = buildIsReadyToPayRequest()
|
||||
val task: Task<Boolean> = paymentsClient.isReadyToPay(request)
|
||||
task.addOnCompleteListener { completedTask ->
|
||||
if (!emitter.isDisposed) {
|
||||
try {
|
||||
val result: Boolean = completedTask.getResult(ApiException::class.java) ?: false
|
||||
if (result) {
|
||||
emitter.onComplete()
|
||||
} else {
|
||||
emitter.onError(Exception("Google Pay is not available."))
|
||||
}
|
||||
} catch (e: ApiException) {
|
||||
emitter.onError(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: JSONException) {
|
||||
emitter.onError(e)
|
||||
}
|
||||
}.subscribeOn(Schedulers.io())
|
||||
fun queryIsReadyToPay(): Completable {
|
||||
return Companion.queryIsReadyToPay(activity, gateway, configuration)
|
||||
}
|
||||
|
||||
/**
|
||||
* Launches the Google Pay sheet via an Activity intent. It is up to the caller to pass
|
||||
|
@ -132,14 +109,6 @@ class GooglePayApi(
|
|||
}
|
||||
}
|
||||
|
||||
private fun buildIsReadyToPayRequest(): IsReadyToPayRequest {
|
||||
val isReadyToPayJson: JSONObject = baseRequest.apply {
|
||||
put("allowedPaymentMethods", JSONArray().put(baseCardPaymentMethod()))
|
||||
}
|
||||
|
||||
return IsReadyToPayRequest.fromJson(isReadyToPayJson.toString())
|
||||
}
|
||||
|
||||
private fun gatewayTokenizationSpecification(): JSONObject {
|
||||
return JSONObject().apply {
|
||||
put("type", "PAYMENT_GATEWAY")
|
||||
|
@ -147,21 +116,8 @@ class GooglePayApi(
|
|||
}
|
||||
}
|
||||
|
||||
private fun baseCardPaymentMethod(): JSONObject {
|
||||
return JSONObject().apply {
|
||||
val parameters = JSONObject().apply {
|
||||
put("allowedAuthMethods", allowedCardAuthMethods)
|
||||
put("allowedCardNetworks", JSONArray(gateway.allowedCardNetworks))
|
||||
put("billingAddressRequired", false)
|
||||
}
|
||||
|
||||
put("type", "CARD")
|
||||
put("parameters", parameters)
|
||||
}
|
||||
}
|
||||
|
||||
private fun cardPaymentMethod(): JSONObject {
|
||||
val cardPaymentMethod = baseCardPaymentMethod()
|
||||
val cardPaymentMethod = baseCardPaymentMethod(gateway)
|
||||
cardPaymentMethod.put("tokenizationSpecification", gatewayTokenizationSpecification())
|
||||
|
||||
return cardPaymentMethod
|
||||
|
@ -181,6 +137,66 @@ class GooglePayApi(
|
|||
put("apiVersion", 2)
|
||||
put("apiVersionMinor", 0)
|
||||
}
|
||||
|
||||
/**
|
||||
* Query the Google Pay API to determine whether or not the device has Google Pay available and ready.
|
||||
*
|
||||
* @return A completable which, when it completes, indicates that Google Pay is available, or when it errors, indicates it is not.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun queryIsReadyToPay(
|
||||
context: Context,
|
||||
gateway: Gateway,
|
||||
configuration: Configuration
|
||||
): Completable = Completable.create { emitter ->
|
||||
val walletOptions = Wallet.WalletOptions.Builder()
|
||||
.setEnvironment(configuration.walletEnvironment)
|
||||
.build()
|
||||
|
||||
val paymentsClient = Wallet.getPaymentsClient(context, walletOptions)
|
||||
|
||||
try {
|
||||
val request: IsReadyToPayRequest = buildIsReadyToPayRequest(gateway)
|
||||
val task: Task<Boolean> = paymentsClient.isReadyToPay(request)
|
||||
task.addOnCompleteListener { completedTask ->
|
||||
if (!emitter.isDisposed) {
|
||||
try {
|
||||
val result: Boolean = completedTask.getResult(ApiException::class.java) ?: false
|
||||
if (result) {
|
||||
emitter.onComplete()
|
||||
} else {
|
||||
emitter.onError(Exception("Google Pay is not available."))
|
||||
}
|
||||
} catch (e: ApiException) {
|
||||
emitter.onError(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: JSONException) {
|
||||
emitter.onError(e)
|
||||
}
|
||||
}.subscribeOn(Schedulers.io())
|
||||
|
||||
private fun buildIsReadyToPayRequest(gateway: Gateway): IsReadyToPayRequest {
|
||||
val isReadyToPayJson: JSONObject = baseRequest.apply {
|
||||
put("allowedPaymentMethods", JSONArray().put(baseCardPaymentMethod(gateway)))
|
||||
}
|
||||
|
||||
return IsReadyToPayRequest.fromJson(isReadyToPayJson.toString())
|
||||
}
|
||||
|
||||
private fun baseCardPaymentMethod(gateway: Gateway): JSONObject {
|
||||
return JSONObject().apply {
|
||||
val parameters = JSONObject().apply {
|
||||
put("allowedAuthMethods", allowedCardAuthMethods)
|
||||
put("allowedCardNetworks", JSONArray(gateway.allowedCardNetworks))
|
||||
put("billingAddressRequired", false)
|
||||
}
|
||||
|
||||
put("type", "CARD")
|
||||
put("parameters", parameters)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -6,7 +6,6 @@ import org.signal.libsignal.zkgroup.receipts.ReceiptCredentialPresentation;
|
|||
import org.signal.libsignal.zkgroup.receipts.ReceiptCredentialRequest;
|
||||
import org.signal.libsignal.zkgroup.receipts.ReceiptCredentialResponse;
|
||||
import org.whispersystems.signalservice.api.groupsv2.GroupsV2Operations;
|
||||
import org.whispersystems.signalservice.api.profiles.SignalServiceProfile;
|
||||
import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException;
|
||||
import org.whispersystems.signalservice.api.subscriptions.ActiveSubscription;
|
||||
import org.whispersystems.signalservice.api.subscriptions.PayPalConfirmPaymentIntentResponse;
|
||||
|
@ -14,21 +13,19 @@ import org.whispersystems.signalservice.api.subscriptions.PayPalCreatePaymentInt
|
|||
import org.whispersystems.signalservice.api.subscriptions.PayPalCreatePaymentMethodResponse;
|
||||
import org.whispersystems.signalservice.api.subscriptions.StripeClientSecret;
|
||||
import org.whispersystems.signalservice.api.subscriptions.SubscriberId;
|
||||
import org.whispersystems.signalservice.api.subscriptions.SubscriptionLevels;
|
||||
import org.whispersystems.signalservice.api.util.CredentialsProvider;
|
||||
import org.whispersystems.signalservice.internal.EmptyResponse;
|
||||
import org.whispersystems.signalservice.internal.ServiceResponse;
|
||||
import org.whispersystems.signalservice.internal.configuration.SignalServiceConfiguration;
|
||||
import org.whispersystems.signalservice.internal.push.DonationProcessor;
|
||||
import org.whispersystems.signalservice.internal.push.DonationsConfiguration;
|
||||
import org.whispersystems.signalservice.internal.push.PushServiceSocket;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.TreeMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import io.reactivex.rxjava3.annotations.NonNull;
|
||||
|
||||
|
@ -41,6 +38,20 @@ public class DonationsService {
|
|||
|
||||
private final PushServiceSocket pushServiceSocket;
|
||||
|
||||
private final AtomicReference<CacheEntry> donationsConfigurationCache = new AtomicReference<>(null);
|
||||
|
||||
private static class CacheEntry {
|
||||
private final DonationsConfiguration donationsConfiguration;
|
||||
private final long expiresAt;
|
||||
private final Locale locale;
|
||||
|
||||
private CacheEntry(DonationsConfiguration donationsConfiguration, long expiresAt, Locale locale) {
|
||||
this.donationsConfiguration = donationsConfiguration;
|
||||
this.expiresAt = expiresAt;
|
||||
this.locale = locale;
|
||||
}
|
||||
}
|
||||
|
||||
public DonationsService(
|
||||
SignalServiceConfiguration configuration,
|
||||
CredentialsProvider credentialsProvider,
|
||||
|
@ -95,61 +106,24 @@ public class DonationsService {
|
|||
return wrapInServiceResponse(() -> new Pair<>(pushServiceSocket.submitBoostReceiptCredentials(paymentIntentId, receiptCredentialRequest, processor), 200));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The suggested amounts for Signal Boost
|
||||
*/
|
||||
public ServiceResponse<Map<String, List<BigDecimal>>> getBoostAmounts() {
|
||||
return wrapInServiceResponse(() -> new Pair<>(pushServiceSocket.getBoostAmounts(), 200));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The badge configuration for signal boost. Expect for right now only a single level numbered 1.
|
||||
*/
|
||||
public ServiceResponse<SignalServiceProfile.Badge> getBoostBadge(Locale locale) {
|
||||
return wrapInServiceResponse(() -> new Pair<>(pushServiceSocket.getBoostLevels(locale).getLevels().get(SubscriptionLevels.BOOST_LEVEL).getBadge(), 200));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return A specific gift badge, by level.
|
||||
*/
|
||||
public ServiceResponse<SignalServiceProfile.Badge> getGiftBadge(Locale locale, long level) {
|
||||
return wrapInServiceResponse(() -> new Pair<>(pushServiceSocket.getBoostLevels(locale).getLevels().get(String.valueOf(level)).getBadge(), 200));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return All gift badges the server currently has available.
|
||||
*/
|
||||
public ServiceResponse<Map<Long, SignalServiceProfile.Badge>> getGiftBadges(Locale locale) {
|
||||
return wrapInServiceResponse(() -> {
|
||||
Map<String, SubscriptionLevels.Level> levels = pushServiceSocket.getBoostLevels(locale).getLevels();
|
||||
Map<Long, SignalServiceProfile.Badge> badges = new TreeMap<>();
|
||||
|
||||
for (Map.Entry<String, SubscriptionLevels.Level> levelEntry : levels.entrySet()) {
|
||||
if (!Objects.equals(levelEntry.getKey(), SubscriptionLevels.BOOST_LEVEL)) {
|
||||
try {
|
||||
badges.put(Long.parseLong(levelEntry.getKey()), levelEntry.getValue().getBadge());
|
||||
} catch (NumberFormatException e) {
|
||||
Log.w(TAG, "Could not parse gift badge for level entry " + levelEntry.getKey(), e);
|
||||
}
|
||||
public ServiceResponse<DonationsConfiguration> getDonationsConfiguration(Locale locale) {
|
||||
CacheEntry cacheEntryOutsideLock = donationsConfigurationCache.get();
|
||||
if (isNewCacheEntryRequired(cacheEntryOutsideLock, locale)) {
|
||||
synchronized (this) {
|
||||
CacheEntry cacheEntryInLock = donationsConfigurationCache.get();
|
||||
if (isNewCacheEntryRequired(cacheEntryInLock, locale)) {
|
||||
return wrapInServiceResponse(() -> {
|
||||
DonationsConfiguration donationsConfiguration = pushServiceSocket.getDonationsConfiguration(locale);
|
||||
donationsConfigurationCache.set(new CacheEntry(donationsConfiguration, System.currentTimeMillis() + TimeUnit.DAYS.toMillis(1), locale));
|
||||
return new Pair<>(donationsConfiguration, 200);
|
||||
});
|
||||
} else {
|
||||
return wrapInServiceResponse(() -> new Pair<>(cacheEntryOutsideLock.donationsConfiguration, 200));
|
||||
}
|
||||
}
|
||||
|
||||
return new Pair<>(badges, 200);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the amounts for the gift badge.
|
||||
*/
|
||||
public ServiceResponse<Map<String, BigDecimal>> getGiftAmount() {
|
||||
return wrapInServiceResponse(() -> new Pair<>(pushServiceSocket.getGiftAmount(), 200));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the subscription levels that are available for the client to choose from along with currencies and current prices
|
||||
*/
|
||||
public ServiceResponse<SubscriptionLevels> getSubscriptionLevels(Locale locale) {
|
||||
return wrapInServiceResponse(() -> new Pair<>(pushServiceSocket.getSubscriptionLevels(locale), 200));
|
||||
} else {
|
||||
return wrapInServiceResponse(() -> new Pair<>(cacheEntryOutsideLock.donationsConfiguration, 200));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -364,6 +338,10 @@ public class DonationsService {
|
|||
}
|
||||
}
|
||||
|
||||
private boolean isNewCacheEntryRequired(CacheEntry cacheEntry, Locale locale) {
|
||||
return cacheEntry == null || cacheEntry.expiresAt < System.currentTimeMillis() || !Objects.equals(locale, cacheEntry.locale);
|
||||
}
|
||||
|
||||
private interface Producer<T> {
|
||||
Pair<T, Integer> produce() throws IOException;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,82 @@
|
|||
package org.whispersystems.signalservice.internal.push;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import org.whispersystems.signalservice.api.profiles.SignalServiceProfile;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Response JSON for a call to /v1/subscriptions/configuration
|
||||
*/
|
||||
public class DonationsConfiguration {
|
||||
|
||||
public static final int BOOST_LEVEL = 1;
|
||||
public static final int GIFT_LEVEL = 100;
|
||||
public static final HashSet<Integer> SUBSCRIPTION_LEVELS = new HashSet<>(Arrays.asList(500, 1000, 2000));
|
||||
|
||||
@JsonProperty("currencies")
|
||||
private Map<String, CurrencyConfiguration> currencies;
|
||||
|
||||
@JsonProperty("levels")
|
||||
private Map<Integer, LevelConfiguration> levels;
|
||||
|
||||
public static class CurrencyConfiguration {
|
||||
@JsonProperty("minimum")
|
||||
private BigDecimal minimum;
|
||||
|
||||
@JsonProperty("oneTime")
|
||||
private Map<Integer, List<BigDecimal>> oneTime;
|
||||
|
||||
@JsonProperty("subscription")
|
||||
private Map<Integer, BigDecimal> subscription;
|
||||
|
||||
@JsonProperty("supportedPaymentMethods")
|
||||
private Set<String> supportedPaymentMethods;
|
||||
|
||||
public BigDecimal getMinimum() {
|
||||
return minimum;
|
||||
}
|
||||
|
||||
public Map<Integer, List<BigDecimal>> getOneTime() {
|
||||
return oneTime;
|
||||
}
|
||||
|
||||
public Map<Integer, BigDecimal> getSubscription() {
|
||||
return subscription;
|
||||
}
|
||||
|
||||
public Set<String> getSupportedPaymentMethods() {
|
||||
return supportedPaymentMethods;
|
||||
}
|
||||
}
|
||||
|
||||
public static class LevelConfiguration {
|
||||
@JsonProperty("name")
|
||||
private String name;
|
||||
|
||||
@JsonProperty("badge")
|
||||
private SignalServiceProfile.Badge badge;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public SignalServiceProfile.Badge getBadge() {
|
||||
return badge;
|
||||
}
|
||||
}
|
||||
|
||||
public Map<String, CurrencyConfiguration> getCurrencies() {
|
||||
return currencies;
|
||||
}
|
||||
|
||||
public Map<Integer, LevelConfiguration> getLevels() {
|
||||
return levels;
|
||||
}
|
||||
}
|
|
@ -264,7 +264,6 @@ public class PushServiceSocket {
|
|||
|
||||
private static final String DONATION_REDEEM_RECEIPT = "/v1/donation/redeem-receipt";
|
||||
|
||||
private static final String SUBSCRIPTION_LEVELS = "/v1/subscription/levels";
|
||||
private static final String UPDATE_SUBSCRIPTION_LEVEL = "/v1/subscription/%s/level/%s/%s/%s";
|
||||
private static final String SUBSCRIPTION = "/v1/subscription/%s";
|
||||
private static final String CREATE_STRIPE_SUBSCRIPTION_PAYMENT_METHOD = "/v1/subscription/%s/create_payment_method";
|
||||
|
@ -272,13 +271,11 @@ public class PushServiceSocket {
|
|||
private static final String DEFAULT_STRIPE_SUBSCRIPTION_PAYMENT_METHOD = "/v1/subscription/%s/default_payment_method/%s";
|
||||
private static final String DEFAULT_PAYPAL_SUBSCRIPTION_PAYMENT_METHOD = "/v1/subscription/%s/default_payment_method/paypal/%s";
|
||||
private static final String SUBSCRIPTION_RECEIPT_CREDENTIALS = "/v1/subscription/%s/receipt_credentials";
|
||||
private static final String BOOST_AMOUNTS = "/v1/subscription/boost/amounts";
|
||||
private static final String GIFT_AMOUNT = "/v1/subscription/boost/amounts/gift";
|
||||
private static final String CREATE_STRIPE_ONE_TIME_PAYMENT_INTENT = "/v1/subscription/boost/create";
|
||||
private static final String CREATE_PAYPAL_ONE_TIME_PAYMENT_INTENT = "/v1/subscription/boost/paypal/create";
|
||||
private static final String CONFIRM_PAYPAL_ONE_TIME_PAYMENT_INTENT = "/v1/subscription/boost/paypal/confirm";
|
||||
private static final String BOOST_RECEIPT_CREDENTIALS = "/v1/subscription/boost/receipt_credentials";
|
||||
private static final String BOOST_BADGES = "/v1/subscription/boost/badges";
|
||||
private static final String DONATIONS_CONFIGURATION = "/v1/subscription/configuration";
|
||||
|
||||
private static final String CDSI_AUTH = "/v2/directory/auth";
|
||||
|
||||
|
@ -1048,28 +1045,10 @@ public class PushServiceSocket {
|
|||
public PayPalCreatePaymentMethodResponse createPayPalPaymentMethod(Locale locale, String subscriberId, String returnUrl, String cancelUrl) throws IOException {
|
||||
Map<String, String> headers = Collections.singletonMap("Accept-Language", locale.getLanguage() + "-" + locale.getCountry());
|
||||
String payload = JsonUtil.toJson(new PayPalCreatePaymentMethodPayload(returnUrl, cancelUrl));
|
||||
String result = makeServiceRequestWithoutAuthentication(String.format(CREATE_PAYPAL_SUBSCRIPTION_PAYMENT_METHOD, subscriberId), "POST", payload);
|
||||
String result = makeServiceRequestWithoutAuthentication(String.format(CREATE_PAYPAL_SUBSCRIPTION_PAYMENT_METHOD, subscriberId), "POST", payload, headers, NO_HANDLER);
|
||||
return JsonUtil.fromJsonResponse(result, PayPalCreatePaymentMethodResponse.class);
|
||||
}
|
||||
|
||||
|
||||
public Map<String, List<BigDecimal>> getBoostAmounts() throws IOException {
|
||||
String result = makeServiceRequestWithoutAuthentication(BOOST_AMOUNTS, "GET", null);
|
||||
TypeReference<HashMap<String, List<BigDecimal>>> typeRef = new TypeReference<HashMap<String, List<BigDecimal>>>() {};
|
||||
return JsonUtil.fromJsonResponse(result, typeRef);
|
||||
}
|
||||
|
||||
public Map<String, BigDecimal> getGiftAmount() throws IOException {
|
||||
String result = makeServiceRequestWithoutAuthentication(GIFT_AMOUNT, "GET", null);
|
||||
TypeReference<HashMap<String, BigDecimal>> typeRef = new TypeReference<HashMap<String, BigDecimal>>() {};
|
||||
return JsonUtil.fromJsonResponse(result, typeRef);
|
||||
}
|
||||
|
||||
public SubscriptionLevels getBoostLevels(Locale locale) throws IOException {
|
||||
String result = makeServiceRequestWithoutAuthentication(BOOST_BADGES, "GET", null, AcceptLanguagesUtil.getHeadersWithAcceptLanguage(locale), NO_HANDLER);
|
||||
return JsonUtil.fromJsonResponse(result, SubscriptionLevels.class);
|
||||
}
|
||||
|
||||
public ReceiptCredentialResponse submitBoostReceiptCredentials(String paymentIntentId, ReceiptCredentialRequest receiptCredentialRequest, DonationProcessor processor) throws IOException {
|
||||
String payload = JsonUtil.toJson(new BoostReceiptCredentialRequestJson(paymentIntentId, receiptCredentialRequest, processor));
|
||||
String response = makeServiceRequestWithoutAuthentication(
|
||||
|
@ -1089,10 +1068,14 @@ public class PushServiceSocket {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the DonationsConfiguration pointed at by /v1/subscriptions/configuration
|
||||
*/
|
||||
public DonationsConfiguration getDonationsConfiguration(Locale locale) throws IOException {
|
||||
Map<String, String> headers = Collections.singletonMap("Accept-Language", locale.getLanguage() + "-" + locale.getCountry());
|
||||
String result = makeServiceRequestWithoutAuthentication(DONATIONS_CONFIGURATION, "GET", null, headers, NO_HANDLER);
|
||||
|
||||
public SubscriptionLevels getSubscriptionLevels(Locale locale) throws IOException {
|
||||
String result = makeServiceRequestWithoutAuthentication(SUBSCRIPTION_LEVELS, "GET", null, AcceptLanguagesUtil.getHeadersWithAcceptLanguage(locale), NO_HANDLER);
|
||||
return JsonUtil.fromJsonResponse(result, SubscriptionLevels.class);
|
||||
return JsonUtil.fromJson(result, DonationsConfiguration.class);
|
||||
}
|
||||
|
||||
public void updateSubscriptionLevel(String subscriberId, String level, String currencyCode, String idempotencyKey) throws IOException {
|
||||
|
|
Loading…
Add table
Reference in a new issue