Move common subscription management out of controller

This commit is contained in:
Ravi Khadiwala
2024-08-14 10:14:18 -05:00
committed by ravi-signal
parent a8eaf2d0ad
commit 97e566d470
21 changed files with 1242 additions and 948 deletions

View File

@@ -52,7 +52,7 @@ import org.whispersystems.textsecuregcm.util.GoogleApiUtil;
import org.whispersystems.textsecuregcm.util.SystemMapper;
import org.whispersystems.textsecuregcm.util.ua.ClientPlatform;
public class BraintreeManager implements SubscriptionProcessorManager {
public class BraintreeManager implements SubscriptionPaymentProcessor {
private static final Logger logger = LoggerFactory.getLogger(BraintreeManager.class);
@@ -124,8 +124,8 @@ public class BraintreeManager implements SubscriptionProcessorManager {
}
@Override
public SubscriptionProcessor getProcessor() {
return SubscriptionProcessor.BRAINTREE;
public PaymentProvider getProvider() {
return PaymentProvider.BRAINTREE;
}
@Override
@@ -211,7 +211,7 @@ public class BraintreeManager implements SubscriptionProcessorManager {
return switch (unsuccessfulTx.getProcessorResponseCode()) {
case GENERIC_DECLINED_PROCESSOR_CODE, PAYPAL_FUNDING_INSTRUMENT_DECLINED_PROCESSOR_CODE ->
CompletableFuture.failedFuture(
new SubscriptionProcessorException(getProcessor(), createChargeFailure(unsuccessfulTx)));
new SubscriptionProcessorException(getProvider(), createChargeFailure(unsuccessfulTx)));
default -> {
logger.info("PayPal charge unexpectedly failed: {}", unsuccessfulTx.getProcessorResponseCode());
@@ -342,7 +342,7 @@ public class BraintreeManager implements SubscriptionProcessorManager {
throw new CompletionException(new BraintreeException(result.getMessage()));
}
return new ProcessorCustomer(result.getTarget().getId(), SubscriptionProcessor.BRAINTREE);
return new ProcessorCustomer(result.getTarget().getId(), PaymentProvider.BRAINTREE);
});
}
@@ -423,7 +423,7 @@ public class BraintreeManager implements SubscriptionProcessorManager {
if (result.getTarget() != null) {
completionException = result.getTarget().getTransactions().stream().findFirst()
.map(transaction -> new CompletionException(
new SubscriptionProcessorException(getProcessor(), createChargeFailure(transaction))))
new SubscriptionProcessorException(getProvider(), createChargeFailure(transaction))))
.orElseGet(() -> new CompletionException(new BraintreeException(result.getMessage())));
} else {
completionException = new CompletionException(new BraintreeException(result.getMessage()));

View File

@@ -12,30 +12,30 @@ import java.util.Map;
/**
* A set of payment providers used for donations
*/
public enum SubscriptionProcessor {
public enum PaymentProvider {
// because provider IDs are stored, they should not be reused, and great care
// must be used if a provider is removed from the list
STRIPE(1),
BRAINTREE(2),
;
private static final Map<Integer, SubscriptionProcessor> IDS_TO_PROCESSORS = new HashMap<>();
private static final Map<Integer, PaymentProvider> IDS_TO_PROCESSORS = new HashMap<>();
static {
Arrays.stream(SubscriptionProcessor.values())
Arrays.stream(PaymentProvider.values())
.forEach(provider -> IDS_TO_PROCESSORS.put((int) provider.id, provider));
}
/**
* @return the provider associated with the given ID, or {@code null} if none exists
*/
public static SubscriptionProcessor forId(byte id) {
public static PaymentProvider forId(byte id) {
return IDS_TO_PROCESSORS.get((int) id);
}
private final byte id;
SubscriptionProcessor(int id) {
PaymentProvider(int id) {
if (id > 255) {
throw new IllegalArgumentException("ID must fit in one byte: " + id);
}

View File

@@ -7,7 +7,7 @@ package org.whispersystems.textsecuregcm.subscriptions;
import java.nio.charset.StandardCharsets;
public record ProcessorCustomer(String customerId, SubscriptionProcessor processor) {
public record ProcessorCustomer(String customerId, PaymentProvider processor) {
public byte[] toDynamoBytes() {
final byte[] customerIdBytes = customerId.getBytes(StandardCharsets.UTF_8);

View File

@@ -76,7 +76,7 @@ import org.slf4j.LoggerFactory;
import org.whispersystems.textsecuregcm.util.Conversions;
import org.whispersystems.textsecuregcm.util.ua.ClientPlatform;
public class StripeManager implements SubscriptionProcessorManager {
public class StripeManager implements SubscriptionPaymentProcessor {
private static final Logger logger = LoggerFactory.getLogger(StripeManager.class);
private static final String METADATA_KEY_LEVEL = "level";
private static final String METADATA_KEY_CLIENT_PLATFORM = "clientPlatform";
@@ -107,8 +107,8 @@ public class StripeManager implements SubscriptionProcessorManager {
}
@Override
public SubscriptionProcessor getProcessor() {
return SubscriptionProcessor.STRIPE;
public PaymentProvider getProvider() {
return PaymentProvider.STRIPE;
}
@Override
@@ -145,7 +145,7 @@ public class StripeManager implements SubscriptionProcessorManager {
throw new CompletionException(e);
}
}, executor)
.thenApply(customer -> new ProcessorCustomer(customer.getId(), getProcessor()));
.thenApply(customer -> new ProcessorCustomer(customer.getId(), getProvider()));
}
public CompletableFuture<Customer> getCustomer(String customerId) {
@@ -300,7 +300,7 @@ public class StripeManager implements SubscriptionProcessorManager {
if (e instanceof CardException ce) {
throw new CompletionException(
new SubscriptionProcessorException(getProcessor(), createChargeFailureFromCardException(e, ce)));
new SubscriptionProcessorException(getProvider(), createChargeFailureFromCardException(e, ce)));
}
throw new CompletionException(e);
@@ -348,7 +348,7 @@ public class StripeManager implements SubscriptionProcessorManager {
if (e instanceof CardException ce) {
throw new CompletionException(
new SubscriptionProcessorException(getProcessor(), createChargeFailureFromCardException(e, ce)));
new SubscriptionProcessorException(getProvider(), createChargeFailureFromCardException(e, ce)));
}
throw new CompletionException(e);
}

View File

@@ -12,10 +12,10 @@ import java.util.concurrent.CompletableFuture;
import javax.annotation.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.textsecuregcm.storage.SubscriptionManager;
import org.whispersystems.textsecuregcm.util.ua.ClientPlatform;
public interface SubscriptionProcessorManager {
SubscriptionProcessor getProcessor();
public interface SubscriptionPaymentProcessor extends SubscriptionManager.Processor {
boolean supportsPaymentMethod(PaymentMethod paymentMethod);
@@ -49,10 +49,6 @@ public interface SubscriptionProcessorManager {
*/
CompletableFuture<LevelAndCurrency> getLevelAndCurrencyForSubscription(Object subscription);
CompletableFuture<Void> cancelAllActiveSubscriptions(String customerId);
CompletableFuture<ReceiptItem> getReceiptItem(String subscriptionId);
CompletableFuture<SubscriptionInformation> getSubscriptionInformation(Object subscription);
enum SubscriptionStatus {
@@ -102,13 +98,13 @@ public interface SubscriptionProcessorManager {
case "incomplete" -> INCOMPLETE;
case "trialing" -> {
final Logger logger = LoggerFactory.getLogger(SubscriptionProcessorManager.class);
final Logger logger = LoggerFactory.getLogger(SubscriptionPaymentProcessor.class);
logger.error("Subscription has status that should never happen: {}", status);
yield UNKNOWN;
}
default -> {
final Logger logger = LoggerFactory.getLogger(SubscriptionProcessorManager.class);
final Logger logger = LoggerFactory.getLogger(SubscriptionPaymentProcessor.class);
logger.error("Subscription has unknown status: {}", status);
yield UNKNOWN;
@@ -137,10 +133,6 @@ public interface SubscriptionProcessorManager {
}
record ReceiptItem(String itemId, Instant paidAt, long level) {
}
record LevelAndCurrency(long level, String currency) {
}

View File

@@ -7,16 +7,16 @@ package org.whispersystems.textsecuregcm.subscriptions;
public class SubscriptionProcessorException extends Exception {
private final SubscriptionProcessor processor;
private final PaymentProvider processor;
private final ChargeFailure chargeFailure;
public SubscriptionProcessorException(final SubscriptionProcessor processor,
public SubscriptionProcessorException(final PaymentProvider processor,
final ChargeFailure chargeFailure) {
this.processor = processor;
this.chargeFailure = chargeFailure;
}
public SubscriptionProcessor getProcessor() {
public PaymentProvider getProcessor() {
return processor;
}