Don't return 204s as exceptions

The jersey LoggingExceptionMapper automatically adds an entity to
WebApplicationExceptions. Jersey's HTTP server later strips the body on
204 responses, but our custom WebSocketResourceProvider does not
This commit is contained in:
Ravi Khadiwala 2024-09-19 16:13:20 -05:00 committed by ravi-signal
parent cd68a674bb
commit bf0f553ced
5 changed files with 31 additions and 11 deletions

View File

@ -54,6 +54,7 @@ import org.whispersystems.textsecuregcm.subscriptions.BraintreeManager;
import org.whispersystems.textsecuregcm.subscriptions.ChargeFailure; import org.whispersystems.textsecuregcm.subscriptions.ChargeFailure;
import org.whispersystems.textsecuregcm.subscriptions.PaymentDetails; import org.whispersystems.textsecuregcm.subscriptions.PaymentDetails;
import org.whispersystems.textsecuregcm.subscriptions.PaymentMethod; import org.whispersystems.textsecuregcm.subscriptions.PaymentMethod;
import org.whispersystems.textsecuregcm.subscriptions.PaymentStatus;
import org.whispersystems.textsecuregcm.subscriptions.StripeManager; import org.whispersystems.textsecuregcm.subscriptions.StripeManager;
import org.whispersystems.textsecuregcm.subscriptions.SubscriptionCurrencyUtil; import org.whispersystems.textsecuregcm.subscriptions.SubscriptionCurrencyUtil;
import org.whispersystems.textsecuregcm.subscriptions.PaymentProvider; import org.whispersystems.textsecuregcm.subscriptions.PaymentProvider;
@ -324,15 +325,15 @@ public class OneTimeDonationController {
return paymentDetailsFut.thenCompose(paymentDetails -> { return paymentDetailsFut.thenCompose(paymentDetails -> {
if (paymentDetails == null) { if (paymentDetails == null) {
throw new WebApplicationException(Response.Status.NOT_FOUND); throw new WebApplicationException(Response.Status.NOT_FOUND);
} } else if (paymentDetails.status() == PaymentStatus.PROCESSING) {
switch (paymentDetails.status()) { return CompletableFuture.completedFuture(Response.noContent().build());
case PROCESSING -> throw new WebApplicationException(Response.Status.NO_CONTENT); } else if (paymentDetails.status() != PaymentStatus.SUCCEEDED) {
case SUCCEEDED -> { throw new WebApplicationException(Response.status(Response.Status.PAYMENT_REQUIRED)
}
default -> throw new WebApplicationException(Response.status(Response.Status.PAYMENT_REQUIRED)
.entity(new CreateBoostReceiptCredentialsErrorResponse(paymentDetails.chargeFailure())).build()); .entity(new CreateBoostReceiptCredentialsErrorResponse(paymentDetails.chargeFailure())).build());
} }
// The payment was successful, try to issue the receipt credential
long level = oneTimeDonationConfiguration.boost().level(); long level = oneTimeDonationConfiguration.boost().level();
if (paymentDetails.customMetadata() != null) { if (paymentDetails.customMetadata() != null) {
String levelMetadata = paymentDetails.customMetadata() String levelMetadata = paymentDetails.customMetadata()

View File

@ -51,6 +51,7 @@ import javax.ws.rs.Path;
import javax.ws.rs.PathParam; import javax.ws.rs.PathParam;
import javax.ws.rs.Produces; import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam; import javax.ws.rs.QueryParam;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.container.ContainerRequestContext; import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.core.Context; import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MediaType;
@ -631,7 +632,10 @@ public class SubscriptionController {
UserAgentTagUtil.getPlatformTag(userAgent))) UserAgentTagUtil.getPlatformTag(userAgent)))
.increment(); .increment();
return Response.ok(new GetReceiptCredentialsResponse(receiptCredentialResponse.serialize())).build(); return Response.ok(new GetReceiptCredentialsResponse(receiptCredentialResponse.serialize())).build();
}); })
.exceptionally(ExceptionUtils.exceptionallyHandler(
SubscriptionException.ReceiptRequestedForOpenPayment.class,
e -> Response.noContent().build()));
} }
@POST @POST

View File

@ -76,6 +76,15 @@ public class SubscriptionException extends Exception {
} }
} }
/**
* 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 static class ProcessorConflict extends SubscriptionException {
public ProcessorConflict(final String message) { public ProcessorConflict(final String message) {
super(null, message); super(null, message);

View File

@ -49,6 +49,8 @@ import org.whispersystems.textsecuregcm.currency.CurrencyConversionManager;
import org.whispersystems.textsecuregcm.http.FaultTolerantHttpClient; import org.whispersystems.textsecuregcm.http.FaultTolerantHttpClient;
import org.whispersystems.textsecuregcm.metrics.MetricsUtil; import org.whispersystems.textsecuregcm.metrics.MetricsUtil;
import org.whispersystems.textsecuregcm.storage.PaymentTime; import org.whispersystems.textsecuregcm.storage.PaymentTime;
import org.whispersystems.textsecuregcm.storage.SubscriptionException;
import org.whispersystems.textsecuregcm.util.ExceptionUtils;
import org.whispersystems.textsecuregcm.util.GoogleApiUtil; import org.whispersystems.textsecuregcm.util.GoogleApiUtil;
import org.whispersystems.textsecuregcm.util.SystemMapper; import org.whispersystems.textsecuregcm.util.SystemMapper;
import org.whispersystems.textsecuregcm.util.ua.ClientPlatform; import org.whispersystems.textsecuregcm.util.ua.ClientPlatform;
@ -611,7 +613,7 @@ public class BraintreeManager implements CustomerAwareSubscriptionPaymentProcess
if (!getPaymentStatus(transaction.getStatus()).equals(PaymentStatus.SUCCEEDED)) { if (!getPaymentStatus(transaction.getStatus()).equals(PaymentStatus.SUCCEEDED)) {
final SubscriptionStatus subscriptionStatus = getSubscriptionStatus(subscription.getStatus(), true); final SubscriptionStatus subscriptionStatus = getSubscriptionStatus(subscription.getStatus(), true);
if (subscriptionStatus.equals(SubscriptionStatus.ACTIVE) || subscriptionStatus.equals(SubscriptionStatus.PAST_DUE)) { if (subscriptionStatus.equals(SubscriptionStatus.ACTIVE) || subscriptionStatus.equals(SubscriptionStatus.PAST_DUE)) {
throw new WebApplicationException(Response.Status.NO_CONTENT); throw ExceptionUtils.wrap(new SubscriptionException.ReceiptRequestedForOpenPayment());
} }
throw new WebApplicationException(Response.status(Response.Status.PAYMENT_REQUIRED) throw new WebApplicationException(Response.status(Response.Status.PAYMENT_REQUIRED)
@ -632,7 +634,7 @@ public class BraintreeManager implements CustomerAwareSubscriptionPaymentProcess
return new ReceiptItem(transaction.getId(), PaymentTime.periodStart(paidAt), metadata.level()); return new ReceiptItem(transaction.getId(), PaymentTime.periodStart(paidAt), metadata.level());
}) })
.orElseThrow(() -> new WebApplicationException(Response.Status.NO_CONTENT))); .orElseThrow(() -> ExceptionUtils.wrap(new SubscriptionException.ReceiptRequestedForOpenPayment())));
} }
private static Subscription getSubscription(Object subscriptionObj) { private static Subscription getSubscription(Object subscriptionObj) {

View File

@ -74,7 +74,9 @@ import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.whispersystems.textsecuregcm.storage.PaymentTime; import org.whispersystems.textsecuregcm.storage.PaymentTime;
import org.whispersystems.textsecuregcm.storage.SubscriptionException;
import org.whispersystems.textsecuregcm.util.Conversions; import org.whispersystems.textsecuregcm.util.Conversions;
import org.whispersystems.textsecuregcm.util.ExceptionUtils;
import org.whispersystems.textsecuregcm.util.ua.ClientPlatform; import org.whispersystems.textsecuregcm.util.ua.ClientPlatform;
public class StripeManager implements CustomerAwareSubscriptionPaymentProcessor { public class StripeManager implements CustomerAwareSubscriptionPaymentProcessor {
@ -607,10 +609,12 @@ public class StripeManager implements CustomerAwareSubscriptionPaymentProcessor
private CompletableFuture<ReceiptItem> convertInvoiceToReceipt(Invoice latestSubscriptionInvoice, String subscriptionId) { private CompletableFuture<ReceiptItem> convertInvoiceToReceipt(Invoice latestSubscriptionInvoice, String subscriptionId) {
if (latestSubscriptionInvoice == null) { if (latestSubscriptionInvoice == null) {
throw new WebApplicationException(Status.NO_CONTENT); return CompletableFuture.failedFuture(
ExceptionUtils.wrap(new SubscriptionException.ReceiptRequestedForOpenPayment()));
} }
if (StringUtils.equalsIgnoreCase("open", latestSubscriptionInvoice.getStatus())) { if (StringUtils.equalsIgnoreCase("open", latestSubscriptionInvoice.getStatus())) {
throw new WebApplicationException(Status.NO_CONTENT); return CompletableFuture.failedFuture(
ExceptionUtils.wrap(new SubscriptionException.ReceiptRequestedForOpenPayment()));
} }
if (!StringUtils.equalsIgnoreCase("paid", latestSubscriptionInvoice.getStatus())) { if (!StringUtils.equalsIgnoreCase("paid", latestSubscriptionInvoice.getStatus())) {
final Response.ResponseBuilder responseBuilder = Response.status(Status.PAYMENT_REQUIRED); final Response.ResponseBuilder responseBuilder = Response.status(Status.PAYMENT_REQUIRED);