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

@@ -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;
}

View File

@@ -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;
}
}

View File

@@ -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 {