Handle donation-driven 440 errors more gracefully.

This commit is contained in:
Alex Hart
2023-10-11 08:56:09 -04:00
committed by GitHub
parent 157d194cc5
commit 520b3a14bc
7 changed files with 171 additions and 26 deletions

View File

@@ -25,6 +25,10 @@ public final class ActiveSubscription {
this.code = code;
}
public String getCode() {
return code;
}
static Processor fromCode(String code) {
for (Processor value : Processor.values()) {
if (value.code.equals(code)) {

View File

@@ -112,6 +112,7 @@ import org.whispersystems.signalservice.internal.contacts.entities.KeyBackupRequ
import org.whispersystems.signalservice.internal.contacts.entities.KeyBackupResponse;
import org.whispersystems.signalservice.internal.contacts.entities.TokenResponse;
import org.whispersystems.signalservice.internal.crypto.AttachmentDigest;
import org.whispersystems.signalservice.internal.push.exceptions.DonationProcessorError;
import org.whispersystems.signalservice.internal.push.exceptions.ForbiddenException;
import org.whispersystems.signalservice.internal.push.exceptions.GroupExistsException;
import org.whispersystems.signalservice.internal.push.exceptions.GroupMismatchedDevicesException;
@@ -1157,7 +1158,7 @@ public class PushServiceSocket {
public PayPalConfirmPaymentIntentResponse confirmPayPalOneTimePaymentIntent(String currency, String amount, long level, String payerId, String paymentId, String paymentToken) throws IOException {
String payload = JsonUtil.toJson(new PayPalConfirmOneTimePaymentIntentPayload(amount, currency, level, payerId, paymentId, paymentToken));
Log.d(TAG, payload);
String result = makeServiceRequestWithoutAuthentication(CONFIRM_PAYPAL_ONE_TIME_PAYMENT_INTENT, "POST", payload);
String result = makeServiceRequestWithoutAuthentication(CONFIRM_PAYPAL_ONE_TIME_PAYMENT_INTENT, "POST", payload, NO_HEADERS, new DonationResponseHandler());
return JsonUtil.fromJsonResponse(result, PayPalConfirmPaymentIntentResponse.class);
}
@@ -1209,7 +1210,7 @@ public class PushServiceSocket {
}
public void updateSubscriptionLevel(String subscriberId, String level, String currencyCode, String idempotencyKey) throws IOException {
makeServiceRequestWithoutAuthentication(String.format(UPDATE_SUBSCRIPTION_LEVEL, subscriberId, level, currencyCode, idempotencyKey), "PUT", "");
makeServiceRequestWithoutAuthentication(String.format(UPDATE_SUBSCRIPTION_LEVEL, subscriberId, level, currencyCode, idempotencyKey), "PUT", "", NO_HEADERS, new DonationResponseHandler());
}
public ActiveSubscription getSubscription(String subscriberId) throws IOException {
@@ -2760,6 +2761,24 @@ public class PushServiceSocket {
makeServiceRequest(String.format(REPORT_SPAM, serviceId.toString(), serverGuid), "POST", JsonUtil.toJson(new SpamTokenMessage(reportingToken)));
}
/**
* Handler for a couple donation endpoints.
*/
private static class DonationResponseHandler implements ResponseCodeHandler {
@Override
public void handle(int responseCode, ResponseBody body) throws NonSuccessfulResponseCodeException, PushNetworkException {
if (responseCode == 440) {
try {
throw JsonUtil.fromJson(body.string(), DonationProcessorError.class);
} catch (IOException e) {
throw new NonSuccessfulResponseCodeException(440);
}
} else {
throw new NonSuccessfulResponseCodeException(responseCode);
}
}
}
private static class RegistrationSessionResponseHandler implements ResponseCodeHandler {
@Override

View File

@@ -0,0 +1,28 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.signalservice.internal.push.exceptions
import com.fasterxml.jackson.annotation.JsonCreator
import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException
import org.whispersystems.signalservice.api.subscriptions.ActiveSubscription.ChargeFailure
import org.whispersystems.signalservice.api.subscriptions.ActiveSubscription.Processor
/**
* HTTP 440 Exception when something bad happens while updating a user's subscription level or
* confirming a PayPal intent.
*/
class DonationProcessorError @JsonCreator constructor(
val processor: Processor,
val chargeFailure: ChargeFailure
) : NonSuccessfulResponseCodeException(440) {
override fun toString(): String {
return """
DonationProcessorError (440)
Processor: $processor
Charge Failure: $chargeFailure
""".trimIndent()
}
}

View File

@@ -0,0 +1,46 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.signalservice.internal.push.exceptions
import org.junit.Assert.assertEquals
import org.junit.Test
import org.whispersystems.signalservice.api.subscriptions.ActiveSubscription
import org.whispersystems.signalservice.internal.util.JsonUtil
class DonationProcessorErrorTest {
companion object {
private val TEST_PROCESSOR = ActiveSubscription.Processor.STRIPE
private const val TEST_CODE = "account_closed"
private const val TEST_MESSAGE = "test_message"
private const val TEST_OUTCOME_NETWORK_STATUS = "test_outcomeNetworkStatus"
private const val TEST_OUTCOME_REASON = "test_outcomeReason"
private const val TEST_OUTCOME_TYPE = "test_outcomeType"
private val TEST_JSON = """
{
"processor": "${TEST_PROCESSOR.code}",
"chargeFailure": {
"code": "$TEST_CODE",
"message": "$TEST_MESSAGE",
"outcomeNetworkStatus": "$TEST_OUTCOME_NETWORK_STATUS",
"outcomeReason": "$TEST_OUTCOME_REASON",
"outcomeType": "$TEST_OUTCOME_TYPE"
}
}
""".trimIndent()
}
@Test
fun givenTestJson_whenIFromJson_thenIExpectProperlyParsedError() {
val result = JsonUtil.fromJson(TEST_JSON, DonationProcessorError::class.java)
assertEquals(TEST_PROCESSOR, result.processor)
assertEquals(TEST_CODE, result.chargeFailure.code)
assertEquals(TEST_OUTCOME_TYPE, result.chargeFailure.outcomeType)
assertEquals(TEST_OUTCOME_NETWORK_STATUS, result.chargeFailure.outcomeNetworkStatus)
}
}