From 237d0fd4e2847b3856798336ffeac1c6f5a826c2 Mon Sep 17 00:00:00 2001 From: Ravi Khadiwala Date: Thu, 19 Sep 2024 17:12:45 -0500 Subject: [PATCH] remove HTTP layer exceptions from Stripe/Braintree managers --- .../textsecuregcm/WhisperServerService.java | 2 - .../mappers/SubscriptionExceptionMapper.java | 32 +++++++++++ .../SubscriptionProcessorExceptionMapper.java | 26 --------- .../storage/SubscriptionException.java | 56 ++++++++++++++++++- .../subscriptions/BraintreeManager.java | 26 +++------ .../subscriptions/StripeManager.java | 50 ++++++++--------- .../SubscriptionProcessorException.java | 26 --------- .../SubscriptionControllerTest.java | 13 ++--- 8 files changed, 123 insertions(+), 108 deletions(-) delete mode 100644 service/src/main/java/org/whispersystems/textsecuregcm/mappers/SubscriptionProcessorExceptionMapper.java delete mode 100644 service/src/main/java/org/whispersystems/textsecuregcm/subscriptions/SubscriptionProcessorException.java diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java b/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java index db981a1eb..fdfa31d86 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java @@ -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; @@ -1217,7 +1216,6 @@ private void registerExceptionMappers(Environment environment, new ImpossiblePhoneNumberExceptionMapper(), new NonNormalizedPhoneNumberExceptionMapper(), new RegistrationServiceSenderExceptionMapper(), - new SubscriptionProcessorExceptionMapper(), new SubscriptionExceptionMapper(), new JsonMappingExceptionMapper() ).forEach(exceptionMapper -> { diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/mappers/SubscriptionExceptionMapper.java b/service/src/main/java/org/whispersystems/textsecuregcm/mappers/SubscriptionExceptionMapper.java index 8d41bb454..0d5f1309a 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/mappers/SubscriptionExceptionMapper.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/mappers/SubscriptionExceptionMapper.java @@ -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 { + @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; @@ -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(); + } } diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/mappers/SubscriptionProcessorExceptionMapper.java b/service/src/main/java/org/whispersystems/textsecuregcm/mappers/SubscriptionProcessorExceptionMapper.java deleted file mode 100644 index 73571ac50..000000000 --- a/service/src/main/java/org/whispersystems/textsecuregcm/mappers/SubscriptionProcessorExceptionMapper.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright 2023 Signal Messenger, LLC - * SPDX-License-Identifier: AGPL-3.0-only - */ - -package org.whispersystems.textsecuregcm.mappers; - -import java.util.Map; -import javax.ws.rs.core.Response; -import javax.ws.rs.ext.ExceptionMapper; -import org.whispersystems.textsecuregcm.subscriptions.SubscriptionProcessorException; - -public class SubscriptionProcessorExceptionMapper implements ExceptionMapper { - - public static final int EXTERNAL_SERVICE_ERROR_STATUS_CODE = 440; - - @Override - public Response toResponse(final SubscriptionProcessorException exception) { - return Response.status(EXTERNAL_SERVICE_ERROR_STATUS_CODE) - .entity(Map.of( - "processor", exception.getProcessor().name(), - "chargeFailure", exception.getChargeFailure() - )) - .build(); - } -} diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/storage/SubscriptionException.java b/service/src/main/java/org/whispersystems/textsecuregcm/storage/SubscriptionException.java index 7eb48e0a0..6ca7107fc 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/storage/SubscriptionException.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/storage/SubscriptionException.java @@ -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 { @@ -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); } diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/subscriptions/BraintreeManager.java b/service/src/main/java/org/whispersystems/textsecuregcm/subscriptions/BraintreeManager.java index a6f8e6458..3f4129f1f 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/subscriptions/BraintreeManager.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/subscriptions/BraintreeManager.java @@ -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; @@ -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; @@ -199,8 +197,7 @@ public CompletableFuture 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(); @@ -214,13 +211,12 @@ public CompletableFuture 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)); @@ -391,7 +387,7 @@ public CompletableFuture 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 maybeExistingSubscription = paymentMethod.getSubscriptions().stream() @@ -426,7 +422,7 @@ public CompletableFuture 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())); @@ -460,9 +456,8 @@ public CompletableFuture 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(); @@ -615,10 +610,7 @@ public CompletableFuture 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(); diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/subscriptions/StripeManager.java b/service/src/main/java/org/whispersystems/textsecuregcm/subscriptions/StripeManager.java index 6623f6d07..095bb289c 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/subscriptions/StripeManager.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/subscriptions/StripeManager.java @@ -40,6 +40,7 @@ import com.stripe.param.SubscriptionUpdateParams; import com.stripe.param.SubscriptionUpdateParams.BillingCycleAnchor; import com.stripe.param.SubscriptionUpdateParams.ProrationBehavior; +import java.io.IOException; import java.nio.charset.StandardCharsets; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; @@ -55,6 +56,7 @@ import java.util.Locale; import java.util.Map; import java.util.Objects; +import java.util.Optional; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; @@ -64,12 +66,6 @@ import javax.annotation.Nullable; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; -import javax.ws.rs.ClientErrorException; -import javax.ws.rs.InternalServerErrorException; -import javax.ws.rs.NotFoundException; -import javax.ws.rs.WebApplicationException; -import javax.ws.rs.core.Response; -import javax.ws.rs.core.Response.Status; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -201,7 +197,8 @@ public Set getSupportedCurrenciesForPaymentMethod(final PaymentMethod pa } /** - * Creates a payment intent. May throw a 400 WebApplicationException if the amount is too small. + * Creates a payment intent. May throw a + * {@link SubscriptionException.AmountTooSmall} if the amount is too small. */ public CompletableFuture createPaymentIntent(final String currency, final long amount, @@ -223,10 +220,7 @@ public CompletableFuture createPaymentIntent(final String currenc return stripeClient.paymentIntents().create(builder.build(), commonOptions()); } catch (StripeException e) { if ("amount_too_small".equalsIgnoreCase(e.getCode())) { - throw new WebApplicationException(Response - .status(Status.BAD_REQUEST) - .entity(Map.of("error", "amount_too_small")) - .build()); + throw ExceptionUtils.wrap(new SubscriptionException.AmountTooSmall()); } else { throw new CompletionException(e); } @@ -303,7 +297,7 @@ public CompletableFuture createSubscription(String customerId, S if (e instanceof CardException ce) { throw new CompletionException( - new SubscriptionProcessorException(getProvider(), createChargeFailureFromCardException(e, ce))); + new SubscriptionException.ProcessorException(getProvider(), createChargeFailureFromCardException(e, ce))); } throw new CompletionException(e); @@ -356,10 +350,10 @@ public CompletableFuture updateSubscription( } catch (StripeException e) { if (e instanceof CardException ce) { - throw new CompletionException( - new SubscriptionProcessorException(getProvider(), createChargeFailureFromCardException(e, ce))); + throw ExceptionUtils.wrap( + new SubscriptionException.ProcessorException(getProvider(), createChargeFailureFromCardException(e, ce))); } - throw new CompletionException(e); + throw ExceptionUtils.wrap(e); } }, executor) @@ -385,8 +379,7 @@ public CompletableFuture getSubscription(String subscriptionId) { public CompletableFuture cancelAllActiveSubscriptions(String customerId) { return getCustomer(customerId).thenCompose(customer -> { if (customer == null) { - throw new InternalServerErrorException( - "no customer record found for id " + customerId); + throw ExceptionUtils.wrap(new IOException("no customer record found for id " + customerId)); } return listNonCanceledSubscriptions(customer); }).thenCompose(subscriptions -> { @@ -617,14 +610,15 @@ private CompletableFuture convertInvoiceToReceipt(Invoice latestSub ExceptionUtils.wrap(new SubscriptionException.ReceiptRequestedForOpenPayment())); } if (!StringUtils.equalsIgnoreCase("paid", latestSubscriptionInvoice.getStatus())) { - final Response.ResponseBuilder responseBuilder = Response.status(Status.PAYMENT_REQUIRED); - if (latestSubscriptionInvoice.getChargeObject() != null) { - final Charge charge = latestSubscriptionInvoice.getChargeObject(); - if (charge.getFailureCode() != null || charge.getFailureMessage() != null) { - responseBuilder.entity(Map.of("chargeFailure", createChargeFailure(charge))); - } - } - throw new WebApplicationException(responseBuilder.build()); + return CompletableFuture.failedFuture(ExceptionUtils.wrap(Optional + .ofNullable(latestSubscriptionInvoice.getChargeObject()) + + // If the charge object has a failure reason we can present to the user, create a detailed exception + .filter(charge -> charge.getFailureCode() != null || charge.getFailureMessage() != null) + . map(charge -> new SubscriptionException.ChargeFailurePaymentRequired(createChargeFailure(charge))) + + // Otherwise, return a generic payment required error + .orElseGet(() -> new SubscriptionException.PaymentRequired()))); } return getInvoiceLineItemsForInvoice(latestSubscriptionInvoice).thenCompose(invoiceLineItems -> { @@ -688,15 +682,15 @@ public CompletableFuture getGeneratedSepaIdFromSetupIntent(String setupI // This usually indicates that the client has made requests out of order, either by not confirming // the SetupIntent or not having the user authorize the transaction. logger.debug("setupIntent {} missing expected fields", setupIntentId); - throw new ClientErrorException(Status.CONFLICT); + throw ExceptionUtils.wrap(new SubscriptionException.ProcessorConflict()); } return setupIntent.getLatestAttemptObject().getPaymentMethodDetails().getIdeal().getGeneratedSepaDebit(); } catch (StripeException e) { if (e.getStatusCode() == 404) { - throw new NotFoundException(); + throw ExceptionUtils.wrap(new SubscriptionException.NotFound()); } logger.error("unexpected error from Stripe when retrieving setupIntent {}", setupIntentId, e); - throw new CompletionException(e); + throw ExceptionUtils.wrap(e); } }, executor); } diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/subscriptions/SubscriptionProcessorException.java b/service/src/main/java/org/whispersystems/textsecuregcm/subscriptions/SubscriptionProcessorException.java deleted file mode 100644 index a771339d4..000000000 --- a/service/src/main/java/org/whispersystems/textsecuregcm/subscriptions/SubscriptionProcessorException.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright 2023 Signal Messenger, LLC - * SPDX-License-Identifier: AGPL-3.0-only - */ - -package org.whispersystems.textsecuregcm.subscriptions; - -public class SubscriptionProcessorException extends Exception { - - private final PaymentProvider processor; - private final ChargeFailure chargeFailure; - - public SubscriptionProcessorException(final PaymentProvider processor, - final ChargeFailure chargeFailure) { - this.processor = processor; - this.chargeFailure = chargeFailure; - } - - public PaymentProvider getProcessor() { - return processor; - } - - public ChargeFailure getChargeFailure() { - return chargeFailure; - } -} diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/controllers/SubscriptionControllerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/controllers/SubscriptionControllerTest.java index 89a6a668d..e2f955afc 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/controllers/SubscriptionControllerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/controllers/SubscriptionControllerTest.java @@ -76,10 +76,10 @@ import org.whispersystems.textsecuregcm.entities.BadgeSvg; import org.whispersystems.textsecuregcm.mappers.CompletionExceptionMapper; import org.whispersystems.textsecuregcm.mappers.SubscriptionExceptionMapper; -import org.whispersystems.textsecuregcm.mappers.SubscriptionProcessorExceptionMapper; import org.whispersystems.textsecuregcm.storage.IssuedReceiptsManager; import org.whispersystems.textsecuregcm.storage.OneTimeDonationsManager; import org.whispersystems.textsecuregcm.storage.PaymentTime; +import org.whispersystems.textsecuregcm.storage.SubscriptionException; import org.whispersystems.textsecuregcm.storage.SubscriptionManager; import org.whispersystems.textsecuregcm.storage.Subscriptions; import org.whispersystems.textsecuregcm.subscriptions.BankMandateTranslator; @@ -93,7 +93,6 @@ import org.whispersystems.textsecuregcm.subscriptions.ProcessorCustomer; import org.whispersystems.textsecuregcm.subscriptions.StripeManager; import org.whispersystems.textsecuregcm.subscriptions.PaymentProvider; -import org.whispersystems.textsecuregcm.subscriptions.SubscriptionProcessorException; import org.whispersystems.textsecuregcm.subscriptions.CustomerAwareSubscriptionPaymentProcessor; import org.whispersystems.textsecuregcm.tests.util.AuthHelper; import org.whispersystems.textsecuregcm.util.MockUtils; @@ -133,7 +132,6 @@ ONETIME_CONFIG, new SubscriptionManager(SUBSCRIPTIONS, List.of(STRIPE_MANAGER, B .addProperty(ServerProperties.UNWRAP_COMPLETION_STAGE_IN_WRITER_ENABLE, Boolean.TRUE) .addProvider(AuthHelper.getAuthFilter()) .addProvider(CompletionExceptionMapper.class) - .addProvider(SubscriptionProcessorExceptionMapper.class) .addProvider(new AuthValueFactoryProvider.Binder<>(AuthenticatedDevice.class)) .addProvider(SubscriptionExceptionMapper.class) .setMapper(SystemMapper.jsonMapper()) @@ -340,7 +338,7 @@ void confirmPaypalBoostProcessorError() { when(BRAINTREE_MANAGER.captureOneTimePayment(anyString(), anyString(), anyString(), anyString(), anyLong(), anyLong(), any())) - .thenReturn(CompletableFuture.failedFuture(new SubscriptionProcessorException(PaymentProvider.BRAINTREE, + .thenReturn(CompletableFuture.failedFuture(new SubscriptionException.ProcessorException(PaymentProvider.BRAINTREE, new ChargeFailure("2046", "Declined", null, null, null)))); final Response response = RESOURCE_EXTENSION.target("/v1/subscription/boost/paypal/confirm") @@ -351,7 +349,7 @@ void confirmPaypalBoostProcessorError() { "currency", "usd", "amount", 123))); - assertThat(response.getStatus()).isEqualTo(SubscriptionProcessorExceptionMapper.EXTERNAL_SERVICE_ERROR_STATUS_CODE); + assertThat(response.getStatus()).isEqualTo(SubscriptionExceptionMapper.PROCESSOR_ERROR_STATUS_CODE); final Map responseMap = response.readEntity(Map.class); assertThat(responseMap.get("processor")).isEqualTo("BRAINTREE"); @@ -419,7 +417,7 @@ void createSubscriptionSuccess() { @Test void createSubscriptionProcessorDeclined() { when(STRIPE_MANAGER.createSubscription(any(), any(), anyLong(), anyLong())) - .thenReturn(CompletableFuture.failedFuture(new SubscriptionProcessorException(PaymentProvider.STRIPE, + .thenReturn(CompletableFuture.failedFuture(new SubscriptionException.ProcessorException(PaymentProvider.STRIPE, new ChargeFailure("card_declined", "Insufficient funds", null, null, null)))); final String level = String.valueOf(levelId); @@ -429,8 +427,7 @@ void createSubscriptionProcessorDeclined() { .request() .put(Entity.json("")); - assertThat(response.getStatus()).isEqualTo( - SubscriptionProcessorExceptionMapper.EXTERNAL_SERVICE_ERROR_STATUS_CODE); + assertThat(response.getStatus()).isEqualTo(SubscriptionExceptionMapper.PROCESSOR_ERROR_STATUS_CODE); final Map responseMap = response.readEntity(Map.class); assertThat(responseMap.get("processor")).isEqualTo("STRIPE");