Handle 429s from play API and add subscription docs

This commit is contained in:
Ravi Khadiwala
2025-07-07 13:36:24 -05:00
committed by ravi-signal
parent 0745cabc87
commit 80c11e7eda
8 changed files with 154 additions and 12 deletions

View File

@@ -992,6 +992,44 @@ class SubscriptionControllerTest {
verify(PLAY_MANAGER, times(1)).cancelAllActiveSubscriptions(oldPurchaseToken);
}
@Test
void createReceiptChargeFailure() throws InvalidInputException, VerificationFailedException {
final byte[] subscriberUserAndKey = new byte[32];
Arrays.fill(subscriberUserAndKey, (byte) 1);
final String subscriberId = Base64.getEncoder().encodeToString(subscriberUserAndKey);
when(CLOCK.instant()).thenReturn(Instant.now());
when(SUBSCRIPTIONS.get(any(), any()))
.thenReturn(CompletableFuture.completedFuture(Subscriptions.GetResult.found(Subscriptions.Record.from(
Arrays.copyOfRange(subscriberUserAndKey, 0, 16),
Map.of(Subscriptions.KEY_PASSWORD, b(new byte[16]),
Subscriptions.KEY_CREATED_AT, n(Instant.now().getEpochSecond()),
Subscriptions.KEY_ACCESSED_AT, n(Instant.now().getEpochSecond()),
Subscriptions.KEY_PROCESSOR_ID_CUSTOMER_ID,
b(new ProcessorCustomer("customer", PaymentProvider.STRIPE).toDynamoBytes()),
Subscriptions.KEY_SUBSCRIPTION_ID, s("subscriptionId"))))));
when(STRIPE_MANAGER.getReceiptItem(any()))
.thenReturn(CompletableFuture.failedFuture(new SubscriptionException.ChargeFailurePaymentRequired(
PaymentProvider.STRIPE,
new ChargeFailure("card_declined", "Insufficient funds", null, null, null))));
final ReceiptCredentialRequest receiptRequest = new ClientZkReceiptOperations(
ServerSecretParams.generate().getPublicParams()).createReceiptCredentialRequestContext(
new ReceiptSerial(new byte[ReceiptSerial.SIZE])).getRequest();
final Response response = RESOURCE_EXTENSION
.target(String.format("/v1/subscription/%s/receipt_credentials", subscriberId))
.request()
.post(Entity.json(new SubscriptionController.GetReceiptCredentialsRequest(receiptRequest.serialize())));
assertThat(response.getStatus()).isEqualTo(402);
final Map responseMap = response.readEntity(Map.class);
assertThat(responseMap.get("processor")).isEqualTo("STRIPE");
assertThat(responseMap.get("chargeFailure")).asInstanceOf(
InstanceOfAssertFactories.map(String.class, Object.class))
.extracting("code")
.isEqualTo("card_declined");
}
@ParameterizedTest
@CsvSource({"5, P45D", "201, P13D"})
public void createReceiptCredential(long level, Duration expectedExpirationWindow)

View File

@@ -42,6 +42,7 @@ import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.EnumSource;
import org.junit.jupiter.params.provider.MethodSource;
import org.whispersystems.textsecuregcm.controllers.RateLimitExceededException;
import org.whispersystems.textsecuregcm.storage.SubscriptionException;
import org.whispersystems.textsecuregcm.util.CompletableFutureTestUtil;
import org.whispersystems.textsecuregcm.util.MockUtils;
@@ -191,6 +192,15 @@ class GooglePlayBillingManagerTest {
verifyNoInteractions(cancel);
}
@Test
public void handle429() throws IOException {
final HttpResponseException mockException = mock(HttpResponseException.class);
when(mockException.getStatusCode()).thenReturn(429);
when(subscriptionsv2Get.execute()).thenThrow(mockException);
CompletableFutureTestUtil.assertFailsWithCause(
RateLimitExceededException.class, googlePlayBillingManager.getSubscriptionInformation(PURCHASE_TOKEN));
}
@Test
public void getReceiptUnacknowledged() throws IOException {
when(subscriptionsv2Get.execute()).thenReturn(new SubscriptionPurchaseV2()