mirror of
https://github.com/signalapp/Signal-Server
synced 2026-04-21 21:38:02 +01:00
Support PayPal for recurring donations
This commit is contained in:
@@ -20,7 +20,6 @@ import com.fasterxml.jackson.dataformat.yaml.YAMLMapper;
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||
import com.stripe.exception.ApiException;
|
||||
import com.stripe.model.PaymentIntent;
|
||||
import com.stripe.model.Subscription;
|
||||
import io.dropwizard.auth.PolymorphicAuthValueFactoryProvider;
|
||||
import io.dropwizard.testing.junit5.DropwizardExtensionsSupport;
|
||||
import io.dropwizard.testing.junit5.ResourceExtension;
|
||||
@@ -65,6 +64,7 @@ import org.whispersystems.textsecuregcm.subscriptions.BraintreeManager;
|
||||
import org.whispersystems.textsecuregcm.subscriptions.ProcessorCustomer;
|
||||
import org.whispersystems.textsecuregcm.subscriptions.StripeManager;
|
||||
import org.whispersystems.textsecuregcm.subscriptions.SubscriptionProcessor;
|
||||
import org.whispersystems.textsecuregcm.subscriptions.SubscriptionProcessorManager;
|
||||
import org.whispersystems.textsecuregcm.tests.util.AuthHelper;
|
||||
import org.whispersystems.textsecuregcm.util.SystemMapper;
|
||||
import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
|
||||
@@ -127,7 +127,6 @@ class SubscriptionControllerTest {
|
||||
|
||||
@Test
|
||||
void testCreateBoostPaymentIntentAmountBelowCurrencyMinimum() {
|
||||
when(STRIPE_MANAGER.convertConfiguredAmountToStripeAmount(any(), any())).thenReturn(new BigDecimal(250));
|
||||
when(STRIPE_MANAGER.supportsCurrency("usd")).thenReturn(true);
|
||||
final Response response = RESOURCE_EXTENSION.target("/v1/subscription/boost/create")
|
||||
.request()
|
||||
@@ -150,24 +149,21 @@ class SubscriptionControllerTest {
|
||||
|
||||
@Test
|
||||
void testCreateBoostPaymentIntentLevelAmountMismatch() {
|
||||
when(STRIPE_MANAGER.convertConfiguredAmountToStripeAmount(any(), any())).thenReturn(new BigDecimal(20));
|
||||
|
||||
final Response response = RESOURCE_EXTENSION.target("/v1/subscription/boost/create")
|
||||
.request()
|
||||
.post(Entity.json("""
|
||||
{
|
||||
"currency": "USD",
|
||||
"amount": 25,
|
||||
"level": 100
|
||||
}
|
||||
"""
|
||||
{
|
||||
"currency": "USD",
|
||||
"amount": 25,
|
||||
"level": 100
|
||||
}
|
||||
"""
|
||||
));
|
||||
assertThat(response.getStatus()).isEqualTo(409);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCreateBoostPaymentIntent() {
|
||||
when(STRIPE_MANAGER.convertConfiguredAmountToStripeAmount(any(), any())).thenReturn(new BigDecimal(300));
|
||||
when(STRIPE_MANAGER.createPaymentIntent(anyString(), anyLong(), anyLong()))
|
||||
.thenReturn(CompletableFuture.completedFuture(PAYMENT_INTENT));
|
||||
when(STRIPE_MANAGER.supportsCurrency("usd")).thenReturn(true);
|
||||
@@ -233,7 +229,7 @@ class SubscriptionControllerTest {
|
||||
@Test
|
||||
void success() {
|
||||
when(STRIPE_MANAGER.createSubscription(any(), any(), anyLong(), anyLong()))
|
||||
.thenReturn(CompletableFuture.completedFuture(mock(Subscription.class)));
|
||||
.thenReturn(CompletableFuture.completedFuture(mock(SubscriptionProcessorManager.SubscriptionId.class)));
|
||||
|
||||
final String level = String.valueOf(levelId);
|
||||
final String idempotencyKey = UUID.randomUUID().toString();
|
||||
@@ -669,37 +665,55 @@ class SubscriptionControllerTest {
|
||||
prices:
|
||||
usd:
|
||||
amount: '5'
|
||||
id: R1
|
||||
processorIds:
|
||||
STRIPE: R1
|
||||
BRAINTREE: M1
|
||||
jpy:
|
||||
amount: '500'
|
||||
id: Q1
|
||||
processorIds:
|
||||
STRIPE: Q1
|
||||
BRAINTREE: N1
|
||||
bif:
|
||||
amount: '5000'
|
||||
id: S1
|
||||
processorIds:
|
||||
STRIPE: S1
|
||||
BRAINTREE: O1
|
||||
15:
|
||||
badge: B2
|
||||
prices:
|
||||
usd:
|
||||
amount: '15'
|
||||
id: R2
|
||||
processorIds:
|
||||
STRIPE: R2
|
||||
BRAINTREE: M2
|
||||
jpy:
|
||||
amount: '1500'
|
||||
id: Q2
|
||||
processorIds:
|
||||
STRIPE: Q2
|
||||
BRAINTREE: N2
|
||||
bif:
|
||||
amount: '15000'
|
||||
id: S2
|
||||
processorIds:
|
||||
STRIPE: S2
|
||||
BRAINTREE: O2
|
||||
35:
|
||||
badge: B3
|
||||
prices:
|
||||
usd:
|
||||
amount: '35'
|
||||
id: R3
|
||||
processorIds:
|
||||
STRIPE: R3
|
||||
BRAINTREE: M3
|
||||
jpy:
|
||||
amount: '3500'
|
||||
id: Q3
|
||||
processorIds:
|
||||
STRIPE: Q3
|
||||
BRAINTREE: N3
|
||||
bif:
|
||||
amount: '35000'
|
||||
id: S3
|
||||
processorIds:
|
||||
STRIPE: S3
|
||||
BRAINTREE: O3
|
||||
""";
|
||||
|
||||
private static final String ONETIME_CONFIG_YAML = """
|
||||
|
||||
@@ -40,6 +40,7 @@ import software.amazon.awssdk.services.dynamodb.model.ScalarAttributeType;
|
||||
class SubscriptionManagerTest {
|
||||
|
||||
private static final long NOW_EPOCH_SECONDS = 1_500_000_000L;
|
||||
private static final Duration DEFAULT_TIMEOUT = Duration.ofSeconds(3);
|
||||
private static final String SUBSCRIPTIONS_TABLE_NAME = "subscriptions";
|
||||
private static final SecureRandom SECURE_RANDOM = new SecureRandom();
|
||||
|
||||
@@ -95,13 +96,13 @@ class SubscriptionManagerTest {
|
||||
Instant created2 = Instant.ofEpochSecond(NOW_EPOCH_SECONDS + 1);
|
||||
|
||||
CompletableFuture<GetResult> getFuture = subscriptionManager.get(user, password1);
|
||||
assertThat(getFuture).succeedsWithin(Duration.ofSeconds(3)).satisfies(getResult -> {
|
||||
assertThat(getFuture).succeedsWithin(DEFAULT_TIMEOUT).satisfies(getResult -> {
|
||||
assertThat(getResult.type).isEqualTo(NOT_STORED);
|
||||
assertThat(getResult.record).isNull();
|
||||
});
|
||||
|
||||
getFuture = subscriptionManager.get(user, password2);
|
||||
assertThat(getFuture).succeedsWithin(Duration.ofSeconds(3)).satisfies(getResult -> {
|
||||
assertThat(getFuture).succeedsWithin(DEFAULT_TIMEOUT).satisfies(getResult -> {
|
||||
assertThat(getResult.type).isEqualTo(NOT_STORED);
|
||||
assertThat(getResult.record).isNull();
|
||||
});
|
||||
@@ -109,35 +110,35 @@ class SubscriptionManagerTest {
|
||||
CompletableFuture<SubscriptionManager.Record> createFuture =
|
||||
subscriptionManager.create(user, password1, created1);
|
||||
Consumer<Record> recordRequirements = checkFreshlyCreatedRecord(user, password1, created1);
|
||||
assertThat(createFuture).succeedsWithin(Duration.ofSeconds(3)).satisfies(recordRequirements);
|
||||
assertThat(createFuture).succeedsWithin(DEFAULT_TIMEOUT).satisfies(recordRequirements);
|
||||
|
||||
// password check fails so this should return null
|
||||
createFuture = subscriptionManager.create(user, password2, created2);
|
||||
assertThat(createFuture).succeedsWithin(Duration.ofSeconds(3)).isNull();
|
||||
assertThat(createFuture).succeedsWithin(DEFAULT_TIMEOUT).isNull();
|
||||
|
||||
// password check matches, but the record already exists so nothing should get updated
|
||||
createFuture = subscriptionManager.create(user, password1, created2);
|
||||
assertThat(createFuture).succeedsWithin(Duration.ofSeconds(3)).satisfies(recordRequirements);
|
||||
assertThat(createFuture).succeedsWithin(DEFAULT_TIMEOUT).satisfies(recordRequirements);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGet() {
|
||||
byte[] wrongUser = getRandomBytes(16);
|
||||
byte[] wrongPassword = getRandomBytes(16);
|
||||
assertThat(subscriptionManager.create(user, password, created)).succeedsWithin(Duration.ofSeconds(3));
|
||||
assertThat(subscriptionManager.create(user, password, created)).succeedsWithin(DEFAULT_TIMEOUT);
|
||||
|
||||
assertThat(subscriptionManager.get(user, password)).succeedsWithin(Duration.ofSeconds(3)).satisfies(getResult -> {
|
||||
assertThat(subscriptionManager.get(user, password)).succeedsWithin(DEFAULT_TIMEOUT).satisfies(getResult -> {
|
||||
assertThat(getResult.type).isEqualTo(FOUND);
|
||||
assertThat(getResult.record).isNotNull().satisfies(checkFreshlyCreatedRecord(user, password, created));
|
||||
});
|
||||
|
||||
assertThat(subscriptionManager.get(user, wrongPassword)).succeedsWithin(Duration.ofSeconds(3))
|
||||
assertThat(subscriptionManager.get(user, wrongPassword)).succeedsWithin(DEFAULT_TIMEOUT)
|
||||
.satisfies(getResult -> {
|
||||
assertThat(getResult.type).isEqualTo(PASSWORD_MISMATCH);
|
||||
assertThat(getResult.record).isNull();
|
||||
});
|
||||
|
||||
assertThat(subscriptionManager.get(wrongUser, password)).succeedsWithin(Duration.ofSeconds(3))
|
||||
assertThat(subscriptionManager.get(wrongUser, password)).succeedsWithin(DEFAULT_TIMEOUT)
|
||||
.satisfies(getResult -> {
|
||||
assertThat(getResult.type).isEqualTo(NOT_STORED);
|
||||
assertThat(getResult.record).isNull();
|
||||
@@ -147,15 +148,15 @@ class SubscriptionManagerTest {
|
||||
@Test
|
||||
void testSetCustomerIdAndProcessor() throws Exception {
|
||||
Instant subscriptionUpdated = Instant.ofEpochSecond(NOW_EPOCH_SECONDS + 1);
|
||||
assertThat(subscriptionManager.create(user, password, created)).succeedsWithin(Duration.ofSeconds(3));
|
||||
assertThat(subscriptionManager.create(user, password, created)).succeedsWithin(DEFAULT_TIMEOUT);
|
||||
|
||||
final CompletableFuture<GetResult> getUser = subscriptionManager.get(user, password);
|
||||
assertThat(getUser).succeedsWithin(Duration.ofSeconds(3));
|
||||
assertThat(getUser).succeedsWithin(DEFAULT_TIMEOUT);
|
||||
final Record userRecord = getUser.get().record;
|
||||
|
||||
assertThat(subscriptionManager.setProcessorAndCustomerId(userRecord,
|
||||
new ProcessorCustomer(customer, SubscriptionProcessor.STRIPE),
|
||||
subscriptionUpdated)).succeedsWithin(Duration.ofSeconds(3))
|
||||
subscriptionUpdated)).succeedsWithin(DEFAULT_TIMEOUT)
|
||||
.hasFieldOrPropertyWithValue("processorCustomer",
|
||||
Optional.of(new ProcessorCustomer(customer, SubscriptionProcessor.STRIPE)));
|
||||
|
||||
@@ -166,7 +167,7 @@ class SubscriptionManagerTest {
|
||||
assertThat(
|
||||
subscriptionManager.setProcessorAndCustomerId(userRecord,
|
||||
new ProcessorCustomer(customer + "1", SubscriptionProcessor.STRIPE),
|
||||
subscriptionUpdated)).failsWithin(Duration.ofSeconds(3))
|
||||
subscriptionUpdated)).failsWithin(DEFAULT_TIMEOUT)
|
||||
.withThrowableOfType(ExecutionException.class)
|
||||
.withCauseInstanceOf(ClientErrorException.class)
|
||||
.extracting(Throwable::getCause)
|
||||
@@ -176,7 +177,7 @@ class SubscriptionManagerTest {
|
||||
assertThat(
|
||||
subscriptionManager.setProcessorAndCustomerId(userRecord,
|
||||
new ProcessorCustomer(customer, SubscriptionProcessor.STRIPE),
|
||||
subscriptionUpdated)).failsWithin(Duration.ofSeconds(3))
|
||||
subscriptionUpdated)).failsWithin(DEFAULT_TIMEOUT)
|
||||
.withThrowableOfType(ExecutionException.class)
|
||||
.withCauseInstanceOf(ClientErrorException.class)
|
||||
.extracting(Throwable::getCause)
|
||||
@@ -184,34 +185,34 @@ class SubscriptionManagerTest {
|
||||
|
||||
assertThat(subscriptionManager.getSubscriberUserByProcessorCustomer(
|
||||
new ProcessorCustomer(customer, SubscriptionProcessor.STRIPE)))
|
||||
.succeedsWithin(Duration.ofSeconds(3)).
|
||||
.succeedsWithin(DEFAULT_TIMEOUT).
|
||||
isEqualTo(user);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLookupByCustomerId() throws Exception {
|
||||
Instant subscriptionUpdated = Instant.ofEpochSecond(NOW_EPOCH_SECONDS + 1);
|
||||
assertThat(subscriptionManager.create(user, password, created)).succeedsWithin(Duration.ofSeconds(3));
|
||||
assertThat(subscriptionManager.create(user, password, created)).succeedsWithin(DEFAULT_TIMEOUT);
|
||||
|
||||
final CompletableFuture<GetResult> getUser = subscriptionManager.get(user, password);
|
||||
assertThat(getUser).succeedsWithin(Duration.ofSeconds(3));
|
||||
assertThat(getUser).succeedsWithin(DEFAULT_TIMEOUT);
|
||||
final Record userRecord = getUser.get().record;
|
||||
|
||||
assertThat(subscriptionManager.setProcessorAndCustomerId(userRecord,
|
||||
new ProcessorCustomer(customer, SubscriptionProcessor.STRIPE),
|
||||
subscriptionUpdated)).succeedsWithin(Duration.ofSeconds(3));
|
||||
subscriptionUpdated)).succeedsWithin(DEFAULT_TIMEOUT);
|
||||
assertThat(subscriptionManager.getSubscriberUserByProcessorCustomer(
|
||||
new ProcessorCustomer(customer, SubscriptionProcessor.STRIPE))).
|
||||
succeedsWithin(Duration.ofSeconds(3)).
|
||||
succeedsWithin(DEFAULT_TIMEOUT).
|
||||
isEqualTo(user);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCanceledAt() {
|
||||
Instant canceled = Instant.ofEpochSecond(NOW_EPOCH_SECONDS + 42);
|
||||
assertThat(subscriptionManager.create(user, password, created)).succeedsWithin(Duration.ofSeconds(3));
|
||||
assertThat(subscriptionManager.canceledAt(user, canceled)).succeedsWithin(Duration.ofSeconds(3));
|
||||
assertThat(subscriptionManager.get(user, password)).succeedsWithin(Duration.ofSeconds(3)).satisfies(getResult -> {
|
||||
assertThat(subscriptionManager.create(user, password, created)).succeedsWithin(DEFAULT_TIMEOUT);
|
||||
assertThat(subscriptionManager.canceledAt(user, canceled)).succeedsWithin(DEFAULT_TIMEOUT);
|
||||
assertThat(subscriptionManager.get(user, password)).succeedsWithin(DEFAULT_TIMEOUT).satisfies(getResult -> {
|
||||
assertThat(getResult).isNotNull();
|
||||
assertThat(getResult.type).isEqualTo(FOUND);
|
||||
assertThat(getResult.record).isNotNull().satisfies(record -> {
|
||||
@@ -227,10 +228,10 @@ class SubscriptionManagerTest {
|
||||
String subscriptionId = Base64.getEncoder().encodeToString(getRandomBytes(16));
|
||||
Instant subscriptionCreated = Instant.ofEpochSecond(NOW_EPOCH_SECONDS + 1);
|
||||
long level = 42;
|
||||
assertThat(subscriptionManager.create(user, password, created)).succeedsWithin(Duration.ofSeconds(3));
|
||||
assertThat(subscriptionManager.create(user, password, created)).succeedsWithin(DEFAULT_TIMEOUT);
|
||||
assertThat(subscriptionManager.subscriptionCreated(user, subscriptionId, subscriptionCreated, level)).
|
||||
succeedsWithin(Duration.ofSeconds(3));
|
||||
assertThat(subscriptionManager.get(user, password)).succeedsWithin(Duration.ofSeconds(3)).satisfies(getResult -> {
|
||||
succeedsWithin(DEFAULT_TIMEOUT);
|
||||
assertThat(subscriptionManager.get(user, password)).succeedsWithin(DEFAULT_TIMEOUT).satisfies(getResult -> {
|
||||
assertThat(getResult).isNotNull();
|
||||
assertThat(getResult.type).isEqualTo(FOUND);
|
||||
assertThat(getResult.record).isNotNull().satisfies(record -> {
|
||||
@@ -247,15 +248,20 @@ class SubscriptionManagerTest {
|
||||
void testSubscriptionLevelChanged() {
|
||||
Instant at = Instant.ofEpochSecond(NOW_EPOCH_SECONDS + 500);
|
||||
long level = 1776;
|
||||
assertThat(subscriptionManager.create(user, password, created)).succeedsWithin(Duration.ofSeconds(3));
|
||||
assertThat(subscriptionManager.subscriptionLevelChanged(user, at, level)).succeedsWithin(Duration.ofSeconds(3));
|
||||
assertThat(subscriptionManager.get(user, password)).succeedsWithin(Duration.ofSeconds(3)).satisfies(getResult -> {
|
||||
String updatedSubscriptionId = "new";
|
||||
assertThat(subscriptionManager.create(user, password, created)).succeedsWithin(DEFAULT_TIMEOUT);
|
||||
assertThat(subscriptionManager.subscriptionCreated(user, "original", created, level - 1)).succeedsWithin(
|
||||
DEFAULT_TIMEOUT);
|
||||
assertThat(subscriptionManager.subscriptionLevelChanged(user, at, level, updatedSubscriptionId)).succeedsWithin(
|
||||
DEFAULT_TIMEOUT);
|
||||
assertThat(subscriptionManager.get(user, password)).succeedsWithin(DEFAULT_TIMEOUT).satisfies(getResult -> {
|
||||
assertThat(getResult).isNotNull();
|
||||
assertThat(getResult.type).isEqualTo(FOUND);
|
||||
assertThat(getResult.record).isNotNull().satisfies(record -> {
|
||||
assertThat(record.accessedAt).isEqualTo(at);
|
||||
assertThat(record.subscriptionLevelChangedAt).isEqualTo(at);
|
||||
assertThat(record.subscriptionLevel).isEqualTo(level);
|
||||
assertThat(record.subscriptionId).isEqualTo(updatedSubscriptionId);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user