Skip to content

Commit

Permalink
remove HTTP layer exceptions from Stripe/Braintree managers
Browse files Browse the repository at this point in the history
  • Loading branch information
ravi-signal committed Sep 24, 2024
1 parent 50bd30f commit 237d0fd
Show file tree
Hide file tree
Showing 8 changed files with 123 additions and 108 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,6 @@
import org.whispersystems.textsecuregcm.mappers.RegistrationServiceSenderExceptionMapper;
import org.whispersystems.textsecuregcm.mappers.ServerRejectedExceptionMapper;
import org.whispersystems.textsecuregcm.mappers.SubscriptionExceptionMapper;
import org.whispersystems.textsecuregcm.mappers.SubscriptionProcessorExceptionMapper;
import org.whispersystems.textsecuregcm.metrics.MessageMetrics;
import org.whispersystems.textsecuregcm.metrics.MetricsApplicationEventListener;
import org.whispersystems.textsecuregcm.metrics.MetricsHttpChannelListener;
Expand Down Expand Up @@ -1217,7 +1216,6 @@ private void registerExceptionMappers(Environment environment,
new ImpossiblePhoneNumberExceptionMapper(),
new NonNormalizedPhoneNumberExceptionMapper(),
new RegistrationServiceSenderExceptionMapper(),
new SubscriptionProcessorExceptionMapper(),
new SubscriptionExceptionMapper(),
new JsonMappingExceptionMapper()
).forEach(exceptionMapper -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,48 @@

package org.whispersystems.textsecuregcm.mappers;

import com.google.common.annotations.VisibleForTesting;
import io.dropwizard.jersey.errors.ErrorMessage;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.ExceptionMapper;
import org.whispersystems.textsecuregcm.storage.SubscriptionException;
import java.util.Map;

public class SubscriptionExceptionMapper implements ExceptionMapper<SubscriptionException> {
@VisibleForTesting
public static final int PROCESSOR_ERROR_STATUS_CODE = 440;

@Override
public Response toResponse(final SubscriptionException exception) {

// Some exceptions have specific error body formats
if (exception instanceof SubscriptionException.AmountTooSmall e) {
return Response
.status(Response.Status.BAD_REQUEST)
.entity(Map.of("error", "amount_too_small"))
.type(MediaType.APPLICATION_JSON_TYPE)
.build();
}
if (exception instanceof SubscriptionException.ProcessorException e) {
return Response.status(PROCESSOR_ERROR_STATUS_CODE)
.entity(Map.of(
"processor", e.getProcessor().name(),
"chargeFailure", e.getChargeFailure()
))
.type(MediaType.APPLICATION_JSON_TYPE)
.build();
}
if (exception instanceof SubscriptionException.ChargeFailurePaymentRequired e) {
return Response
.status(Response.Status.PAYMENT_REQUIRED)
.entity(Map.of("chargeFailure", e.getChargeFailure()))
.type(MediaType.APPLICATION_JSON_TYPE)
.build();
}

// Otherwise, we'll return a generic error message WebApplicationException, with a detailed error if one is provided
final Response.Status status = (switch (exception) {
case SubscriptionException.NotFound e -> Response.Status.NOT_FOUND;
case SubscriptionException.Forbidden e -> Response.Status.FORBIDDEN;
Expand All @@ -36,5 +67,6 @@ public Response toResponse(final SubscriptionException exception) {
.fromResponse(wae.getResponse())
.type(MediaType.APPLICATION_JSON_TYPE)
.entity(new ErrorMessage(wae.getResponse().getStatus(), wae.getLocalizedMessage())).build();

}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

import java.util.Optional;
import javax.annotation.Nullable;
import org.whispersystems.textsecuregcm.subscriptions.ChargeFailure;
import org.whispersystems.textsecuregcm.subscriptions.PaymentProvider;

public class SubscriptionException extends Exception {

Expand Down Expand Up @@ -53,39 +55,91 @@ public InvalidArguments(final String message, final Exception cause) {
}

public static class InvalidLevel extends InvalidArguments {

public InvalidLevel() {
super(null, null);
}
}

public static class AmountTooSmall extends InvalidArguments {

public AmountTooSmall() {
super(null, null);
}
}

public static class PaymentRequiresAction extends InvalidArguments {

public PaymentRequiresAction(String message) {
super(message, null);
}

public PaymentRequiresAction() {
super(null, null);
}
}

public static class PaymentRequired extends SubscriptionException {

public PaymentRequired() {
super(null, null);
}

public PaymentRequired(String message) {
super(null, message);
}
}

public static class ChargeFailurePaymentRequired extends SubscriptionException {

private final ChargeFailure chargeFailure;

public ChargeFailurePaymentRequired(final ChargeFailure chargeFailure) {
super(null, null);
this.chargeFailure = chargeFailure;
}

public ChargeFailure getChargeFailure() {
return chargeFailure;
}
}

public static class ProcessorException extends SubscriptionException {

private final PaymentProvider processor;
private final ChargeFailure chargeFailure;

public ProcessorException(final PaymentProvider processor, final ChargeFailure chargeFailure) {
super(null, null);
this.processor = processor;
this.chargeFailure = chargeFailure;
}

public PaymentProvider getProcessor() {
return processor;
}

public ChargeFailure getChargeFailure() {
return chargeFailure;
}
}

/**
* Attempted to retrieve a receipt for a subscription that hasn't yet been charged or the invoice is in the open state
* Attempted to retrieve a receipt for a subscription that hasn't yet been charged or the invoice is in the open
* state
*/
public static class ReceiptRequestedForOpenPayment extends SubscriptionException {

public ReceiptRequestedForOpenPayment() {
super(null, null);
}
}

public static class ProcessorConflict extends SubscriptionException {
public ProcessorConflict() {
super(null, null);
}

public ProcessorConflict(final String message) {
super(null, message);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import com.google.common.annotations.VisibleForTesting;
import com.google.pubsub.v1.PubsubMessage;
import io.micrometer.core.instrument.Metrics;
import java.io.IOException;
import java.math.BigDecimal;
import java.time.Duration;
import java.time.Instant;
Expand All @@ -39,9 +40,6 @@
import java.util.concurrent.Executor;
import java.util.concurrent.ScheduledExecutorService;
import javax.annotation.Nullable;
import javax.ws.rs.ClientErrorException;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.textsecuregcm.configuration.CircuitBreakerConfiguration;
Expand Down Expand Up @@ -199,8 +197,7 @@ public CompletableFuture<PayPalChargeSuccessDetails> captureOneTimePayment(Strin
);

if (search.getMaximumSize() == 0) {
return CompletableFuture.failedFuture(
new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR));
return CompletableFuture.failedFuture(ExceptionUtils.wrap(new IOException()));
}

final Transaction successfulTx = search.getFirst();
Expand All @@ -214,13 +211,12 @@ public CompletableFuture<PayPalChargeSuccessDetails> captureOneTimePayment(Strin
return switch (unsuccessfulTx.getProcessorResponseCode()) {
case GENERIC_DECLINED_PROCESSOR_CODE, PAYPAL_FUNDING_INSTRUMENT_DECLINED_PROCESSOR_CODE ->
CompletableFuture.failedFuture(
new SubscriptionProcessorException(getProvider(), createChargeFailure(unsuccessfulTx)));
new SubscriptionException.ProcessorException(getProvider(), createChargeFailure(unsuccessfulTx)));

default -> {
logger.info("PayPal charge unexpectedly failed: {}", unsuccessfulTx.getProcessorResponseCode());

yield CompletableFuture.failedFuture(
new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR));
yield CompletableFuture.failedFuture(ExceptionUtils.wrap(new IOException()));
}
};
}, executor));
Expand Down Expand Up @@ -391,7 +387,7 @@ public CompletableFuture<SubscriptionId> createSubscription(String customerId, S
return getDefaultPaymentMethod(customerId)
.thenCompose(paymentMethod -> {
if (paymentMethod == null) {
throw new ClientErrorException(Response.Status.CONFLICT);
throw ExceptionUtils.wrap(new SubscriptionException.ProcessorConflict());
}

final Optional<Subscription> maybeExistingSubscription = paymentMethod.getSubscriptions().stream()
Expand Down Expand Up @@ -426,7 +422,7 @@ public CompletableFuture<SubscriptionId> createSubscription(String customerId, S
if (result.getTarget() != null) {
completionException = result.getTarget().getTransactions().stream().findFirst()
.map(transaction -> new CompletionException(
new SubscriptionProcessorException(getProvider(), createChargeFailure(transaction))))
new SubscriptionException.ProcessorException(getProvider(), createChargeFailure(transaction))))
.orElseGet(() -> new CompletionException(new BraintreeException(result.getMessage())));
} else {
completionException = new CompletionException(new BraintreeException(result.getMessage()));
Expand Down Expand Up @@ -460,9 +456,8 @@ public CompletableFuture<SubscriptionId> updateSubscription(Object subscriptionO
return cancelSubscriptionAtEndOfCurrentPeriod(subscription)
.thenCompose(ignored -> {

final Transaction transaction = getLatestTransactionForSubscription(subscription).orElseThrow(
() -> new ClientErrorException(
Response.Status.CONFLICT));
final Transaction transaction = getLatestTransactionForSubscription(subscription)
.orElseThrow(() -> ExceptionUtils.wrap(new SubscriptionException.ProcessorConflict()));

final Customer customer = transaction.getCustomer();

Expand Down Expand Up @@ -615,10 +610,7 @@ public CompletableFuture<ReceiptItem> getReceiptItem(String subscriptionId) {
if (subscriptionStatus.equals(SubscriptionStatus.ACTIVE) || subscriptionStatus.equals(SubscriptionStatus.PAST_DUE)) {
throw ExceptionUtils.wrap(new SubscriptionException.ReceiptRequestedForOpenPayment());
}

throw new WebApplicationException(Response.status(Response.Status.PAYMENT_REQUIRED)
.entity(Map.of("chargeFailure", createChargeFailure(transaction)))
.build());
throw ExceptionUtils.wrap(new SubscriptionException.ChargeFailurePaymentRequired(createChargeFailure(transaction)));
}

final Instant paidAt = transaction.getSubscriptionDetails().getBillingPeriodStartDate().toInstant();
Expand Down
Loading

0 comments on commit 237d0fd

Please sign in to comment.