Handle potentially null payment method when canceling subscription
This commit is contained in:
parent
47ad5779ad
commit
487b5edc75
|
@ -21,6 +21,7 @@ import com.braintreegateway.exceptions.NotFoundException;
|
||||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.HexFormat;
|
import java.util.HexFormat;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -35,6 +36,7 @@ import javax.annotation.Nullable;
|
||||||
import javax.ws.rs.ClientErrorException;
|
import javax.ws.rs.ClientErrorException;
|
||||||
import javax.ws.rs.WebApplicationException;
|
import javax.ws.rs.WebApplicationException;
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.whispersystems.textsecuregcm.configuration.CircuitBreakerConfiguration;
|
import org.whispersystems.textsecuregcm.configuration.CircuitBreakerConfiguration;
|
||||||
|
@ -61,18 +63,26 @@ public class BraintreeManager implements SubscriptionProcessorManager {
|
||||||
final CircuitBreakerConfiguration circuitBreakerConfiguration,
|
final CircuitBreakerConfiguration circuitBreakerConfiguration,
|
||||||
final Executor executor) {
|
final Executor executor) {
|
||||||
|
|
||||||
this.braintreeGateway = new BraintreeGateway(braintreeEnvironment, braintreeMerchantId, braintreePublicKey,
|
this(new BraintreeGateway(braintreeEnvironment, braintreeMerchantId, braintreePublicKey,
|
||||||
braintreePrivateKey);
|
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.supportedCurrencies = supportedCurrencies;
|
||||||
this.currenciesToMerchantAccounts = currenciesToMerchantAccounts;
|
this.currenciesToMerchantAccounts = currenciesToMerchantAccounts;
|
||||||
|
this.braintreeGraphqlClient = braintreeGraphqlClient;
|
||||||
final FaultTolerantHttpClient httpClient = FaultTolerantHttpClient.newBuilder()
|
|
||||||
.withName("braintree-graphql")
|
|
||||||
.withCircuitBreaker(circuitBreakerConfiguration)
|
|
||||||
.withExecutor(executor)
|
|
||||||
.build();
|
|
||||||
this.braintreeGraphqlClient = new BraintreeGraphqlClient(httpClient, graphqlUri, braintreePublicKey,
|
|
||||||
braintreePrivateKey);
|
|
||||||
this.executor = executor;
|
this.executor = executor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -96,7 +106,6 @@ public class BraintreeManager implements SubscriptionProcessorManager {
|
||||||
return supportedCurrencies.contains(currency.toLowerCase(Locale.ROOT));
|
return supportedCurrencies.contains(currency.toLowerCase(Locale.ROOT));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CompletableFuture<PaymentDetails> getPaymentDetails(final String paymentId) {
|
public CompletableFuture<PaymentDetails> getPaymentDetails(final String paymentId) {
|
||||||
return CompletableFuture.supplyAsync(() -> {
|
return CompletableFuture.supplyAsync(() -> {
|
||||||
|
@ -446,7 +455,10 @@ public class BraintreeManager implements SubscriptionProcessorManager {
|
||||||
|
|
||||||
return CompletableFuture.supplyAsync(() -> braintreeGateway.customer().find(customerId), executor).thenCompose(customer -> {
|
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)
|
.map(this::cancelSubscriptionAtEndOfCurrentPeriod)
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue