Handle potentially null payment method when canceling subscription

This commit is contained in:
Chris Eager 2023-04-25 17:12:16 -05:00 committed by Chris Eager
parent 47ad5779ad
commit 487b5edc75
2 changed files with 76 additions and 12 deletions

View File

@ -21,6 +21,7 @@ import com.braintreegateway.exceptions.NotFoundException;
import com.fasterxml.jackson.core.JsonProcessingException;
import java.math.BigDecimal;
import java.time.Instant;
import java.util.Collections;
import java.util.Comparator;
import java.util.HexFormat;
import java.util.List;
@ -35,6 +36,7 @@ import javax.annotation.Nullable;
import javax.ws.rs.ClientErrorException;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;
import com.google.common.annotations.VisibleForTesting;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.textsecuregcm.configuration.CircuitBreakerConfiguration;
@ -61,18 +63,26 @@ public class BraintreeManager implements SubscriptionProcessorManager {
final CircuitBreakerConfiguration circuitBreakerConfiguration,
final Executor executor) {
this.braintreeGateway = new BraintreeGateway(braintreeEnvironment, braintreeMerchantId, braintreePublicKey,
braintreePrivateKey);
this(new BraintreeGateway(braintreeEnvironment, braintreeMerchantId, braintreePublicKey,
braintreePrivateKey),
supportedCurrencies,
currenciesToMerchantAccounts,
new BraintreeGraphqlClient(FaultTolerantHttpClient.newBuilder()
.withName("braintree-graphql")
.withCircuitBreaker(circuitBreakerConfiguration)
.withExecutor(executor)
.build(), graphqlUri, braintreePublicKey, braintreePrivateKey),
executor);
}
@VisibleForTesting
BraintreeManager(final BraintreeGateway braintreeGateway, final Set<String> supportedCurrencies,
final Map<String, String> currenciesToMerchantAccounts, final BraintreeGraphqlClient braintreeGraphqlClient,
final Executor executor) {
this.braintreeGateway = braintreeGateway;
this.supportedCurrencies = supportedCurrencies;
this.currenciesToMerchantAccounts = currenciesToMerchantAccounts;
final FaultTolerantHttpClient httpClient = FaultTolerantHttpClient.newBuilder()
.withName("braintree-graphql")
.withCircuitBreaker(circuitBreakerConfiguration)
.withExecutor(executor)
.build();
this.braintreeGraphqlClient = new BraintreeGraphqlClient(httpClient, graphqlUri, braintreePublicKey,
braintreePrivateKey);
this.braintreeGraphqlClient = braintreeGraphqlClient;
this.executor = executor;
}
@ -96,7 +106,6 @@ public class BraintreeManager implements SubscriptionProcessorManager {
return supportedCurrencies.contains(currency.toLowerCase(Locale.ROOT));
}
@Override
public CompletableFuture<PaymentDetails> getPaymentDetails(final String paymentId) {
return CompletableFuture.supplyAsync(() -> {
@ -446,7 +455,10 @@ public class BraintreeManager implements SubscriptionProcessorManager {
return CompletableFuture.supplyAsync(() -> braintreeGateway.customer().find(customerId), executor).thenCompose(customer -> {
final List<CompletableFuture<Void>> subscriptionCancelFutures = customer.getDefaultPaymentMethod().getSubscriptions().stream()
final List<CompletableFuture<Void>> subscriptionCancelFutures = Optional.ofNullable(customer.getDefaultPaymentMethod())
.map(com.braintreegateway.PaymentMethod::getSubscriptions)
.orElse(Collections.emptyList())
.stream()
.map(this::cancelSubscriptionAtEndOfCurrentPeriod)
.toList();

View File

@ -0,0 +1,52 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.subscriptions;
import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import com.braintreegateway.BraintreeGateway;
import com.braintreegateway.Customer;
import com.braintreegateway.CustomerGateway;
import java.time.Duration;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Executors;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
class BraintreeManagerTest {
private BraintreeGateway braintreeGateway;
private BraintreeManager braintreeManager;
@BeforeEach
void setup() {
braintreeGateway = mock(BraintreeGateway.class);
braintreeManager = new BraintreeManager(braintreeGateway,
Set.of("usd"),
Map.of("usd", "usdMerchant"),
mock(BraintreeGraphqlClient.class),
Executors.newSingleThreadExecutor());
}
@Test
void cancelAllActiveSubscriptions_nullDefaultPaymentMethod() {
final Customer customer = mock(Customer.class);
when(customer.getDefaultPaymentMethod()).thenReturn(null);
final CustomerGateway customerGateway = mock(CustomerGateway.class);
when(customerGateway.find(anyString())).thenReturn(customer);
when(braintreeGateway.customer()).thenReturn(customerGateway);
assertTimeoutPreemptively(Duration.ofSeconds(5), () ->
braintreeManager.cancelAllActiveSubscriptions("customerId")).join();
}
}