mirror of
https://github.com/signalapp/Signal-Android.git
synced 2025-12-24 13:08:46 +00:00
Implement new APIs for Boost badging.
This commit is contained in:
committed by
Greyson Parrelli
parent
48a81da883
commit
186bd9db48
@@ -6,6 +6,7 @@ import org.signal.zkgroup.receipts.ReceiptCredentialResponse;
|
||||
import org.whispersystems.libsignal.logging.Log;
|
||||
import org.whispersystems.libsignal.util.Pair;
|
||||
import org.whispersystems.signalservice.api.groupsv2.GroupsV2Operations;
|
||||
import org.whispersystems.signalservice.api.profiles.SignalServiceProfile;
|
||||
import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException;
|
||||
import org.whispersystems.signalservice.api.subscriptions.ActiveSubscription;
|
||||
import org.whispersystems.signalservice.api.subscriptions.SubscriberId;
|
||||
@@ -19,6 +20,9 @@ import org.whispersystems.signalservice.internal.push.DonationIntentResult;
|
||||
import org.whispersystems.signalservice.internal.push.PushServiceSocket;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import io.reactivex.rxjava3.annotations.NonNull;
|
||||
import io.reactivex.rxjava3.core.Single;
|
||||
@@ -73,8 +77,33 @@ public class DonationsService {
|
||||
* @param currencyCode The currency code for the amount
|
||||
* @return A ServiceResponse containing a DonationIntentResult with details given to us by the payment gateway.
|
||||
*/
|
||||
public Single<ServiceResponse<DonationIntentResult>> createDonationIntentWithAmount(String amount, String currencyCode) {
|
||||
return createServiceResponse(() -> new Pair<>(pushServiceSocket.createDonationIntentWithAmount(amount, currencyCode), 200));
|
||||
public Single<ServiceResponse<SubscriptionClientSecret>> createDonationIntentWithAmount(String amount, String currencyCode) {
|
||||
return createServiceResponse(() -> new Pair<>(pushServiceSocket.createBoostPaymentMethod(currencyCode, Long.parseLong(amount)), 200));
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a completed payment intent and a receipt credential request produces a receipt credential response.
|
||||
* Clients should always use the same ReceiptCredentialRequest with the same payment intent id. This request is repeatable so long as the two values are reused.
|
||||
*
|
||||
* @param paymentIntentId PaymentIntent ID from a boost donation intent response.
|
||||
* @param receiptCredentialRequest Client-generated request token
|
||||
*/
|
||||
public Single<ServiceResponse<ReceiptCredentialResponse>> submitBoostReceiptCredentialRequest(String paymentIntentId, ReceiptCredentialRequest receiptCredentialRequest) {
|
||||
return createServiceResponse(() -> new Pair<>(pushServiceSocket.submitBoostReceiptCredentials(paymentIntentId, receiptCredentialRequest), 200));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The suggested amounts for Signal Boost
|
||||
*/
|
||||
public Single<ServiceResponse<Map<String, List<BigDecimal>>>> getBoostAmounts() {
|
||||
return createServiceResponse(() -> new Pair<>(pushServiceSocket.getBoostAmounts(), 200));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The badge configuration for signal boost. Expect for right now only a single level numbered 1.
|
||||
*/
|
||||
public Single<ServiceResponse<SignalServiceProfile.Badge>> getBoostBadge() {
|
||||
return createServiceResponse(() -> new Pair<>(pushServiceSocket.getBoostLevels().getLevels().get("1").getBadge(), 200));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -8,6 +8,8 @@ import org.whispersystems.signalservice.internal.websocket.WebsocketResponse;
|
||||
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
import io.reactivex.rxjava3.core.Single;
|
||||
|
||||
/**
|
||||
* Encapsulates a parsed APi response regardless of where it came from (WebSocket or REST). Not only
|
||||
* includes the success result but also any application errors encountered (404s, parsing, etc.) or
|
||||
@@ -68,6 +70,18 @@ public final class ServiceResponse<Result> {
|
||||
return executionError;
|
||||
}
|
||||
|
||||
public Single<Result> flattenResult() {
|
||||
if (result.isPresent()) {
|
||||
return Single.just(result.get());
|
||||
} else if (applicationError.isPresent()) {
|
||||
return Single.error(applicationError.get());
|
||||
} else if (executionError.isPresent()) {
|
||||
return Single.error(executionError.get());
|
||||
} else {
|
||||
return Single.error(new AssertionError("Should never get here."));
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> ServiceResponse<T> forResult(T result, WebsocketResponse response) {
|
||||
return new ServiceResponse<>(result, response);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package org.whispersystems.signalservice.internal.push;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import org.signal.zkgroup.receipts.ReceiptCredentialRequest;
|
||||
import org.whispersystems.util.Base64;
|
||||
|
||||
class BoostReceiptCredentialRequestJson {
|
||||
@JsonProperty("paymentIntentId")
|
||||
private final String paymentIntentId;
|
||||
|
||||
@JsonProperty("receiptCredentialRequest")
|
||||
private final String receiptCredentialRequest;
|
||||
|
||||
BoostReceiptCredentialRequestJson(String paymentIntentId, ReceiptCredentialRequest receiptCredentialRequest) {
|
||||
this.paymentIntentId = paymentIntentId;
|
||||
this.receiptCredentialRequest = Base64.encodeBytes(receiptCredentialRequest.serialize());
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ package org.whispersystems.signalservice.internal.push;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.google.protobuf.InvalidProtocolBufferException;
|
||||
import com.google.protobuf.MessageLite;
|
||||
|
||||
@@ -133,6 +134,7 @@ import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.math.BigDecimal;
|
||||
import java.security.KeyManagementException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
@@ -242,7 +244,6 @@ public class PushServiceSocket {
|
||||
private static final String SUBMIT_RATE_LIMIT_CHALLENGE = "/v1/challenge";
|
||||
private static final String REQUEST_RATE_LIMIT_PUSH_CHALLENGE = "/v1/challenge/push";
|
||||
|
||||
private static final String DONATION_INTENT = "/v1/donation/authorize-apple-pay";
|
||||
private static final String DONATION_REDEEM_RECEIPT = "/v1/donation/redeem-receipt";
|
||||
|
||||
private static final String SUBSCRIPTION_LEVELS = "/v1/subscription/levels";
|
||||
@@ -251,6 +252,10 @@ public class PushServiceSocket {
|
||||
private static final String CREATE_SUBSCRIPTION_PAYMENT_METHOD = "/v1/subscription/%s/create_payment_method";
|
||||
private static final String DEFAULT_SUBSCRIPTION_PAYMENT_METHOD = "/v1/subscription/%s/default_payment_method/%s";
|
||||
private static final String SUBSCRIPTION_RECEIPT_CREDENTIALS = "/v1/subscription/%s/receipt_credentials";
|
||||
private static final String BOOST_AMOUNTS = "/v1/subscription/boost/amounts";
|
||||
private static final String CREATE_BOOST_PAYMENT_INTENT = "/v1/subscription/boost/create";
|
||||
private static final String BOOST_RECEIPT_CREDENTIALS = "/v1/subscription/boost/receipt_credentials";
|
||||
private static final String BOOST_BADGES = "/v1/subscription/boost/badges";
|
||||
|
||||
private static final String REPORT_SPAM = "/v1/messages/report/%s/%s";
|
||||
|
||||
@@ -870,15 +875,42 @@ public class PushServiceSocket {
|
||||
makeServiceRequest(DONATION_REDEEM_RECEIPT, "POST", payload);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The PaymentIntent id
|
||||
*/
|
||||
public DonationIntentResult createDonationIntentWithAmount(String amount, String currencyCode) throws IOException {
|
||||
String payload = JsonUtil.toJson(new DonationIntentPayload(Long.parseLong(amount), currencyCode.toLowerCase(Locale.ROOT)));
|
||||
String result = makeServiceRequest(DONATION_INTENT, "POST", payload);
|
||||
return JsonUtil.fromJsonResponse(result, DonationIntentResult.class);
|
||||
public SubscriptionClientSecret createBoostPaymentMethod(String currencyCode, long amount) throws IOException {
|
||||
String payload = JsonUtil.toJson(new DonationIntentPayload(amount, currencyCode));
|
||||
String result = makeServiceRequestWithoutAuthentication(CREATE_BOOST_PAYMENT_INTENT, "POST", payload);
|
||||
return JsonUtil.fromJsonResponse(result, SubscriptionClientSecret.class);
|
||||
}
|
||||
|
||||
public Map<String, List<BigDecimal>> getBoostAmounts() throws IOException {
|
||||
String result = makeServiceRequestWithoutAuthentication(BOOST_AMOUNTS, "GET", null);
|
||||
TypeReference<HashMap<String, List<BigDecimal>>> typeRef = new TypeReference<HashMap<String, List<BigDecimal>>>() {};
|
||||
return JsonUtil.fromJsonResponse(result, typeRef);
|
||||
}
|
||||
|
||||
public SubscriptionLevels getBoostLevels() throws IOException {
|
||||
String result = makeServiceRequestWithoutAuthentication(BOOST_BADGES, "GET", null);
|
||||
return JsonUtil.fromJsonResponse(result, SubscriptionLevels.class);
|
||||
}
|
||||
|
||||
public ReceiptCredentialResponse submitBoostReceiptCredentials(String paymentIntentId, ReceiptCredentialRequest receiptCredentialRequest) throws IOException {
|
||||
String payload = JsonUtil.toJson(new BoostReceiptCredentialRequestJson(paymentIntentId, receiptCredentialRequest));
|
||||
String response = makeServiceRequestWithoutAuthentication(
|
||||
BOOST_RECEIPT_CREDENTIALS,
|
||||
"POST",
|
||||
payload,
|
||||
(code, body) -> {
|
||||
if (code == 204) throw new NonSuccessfulResponseCodeException(204);
|
||||
});
|
||||
|
||||
ReceiptCredentialResponseJson responseJson = JsonUtil.fromJson(response, ReceiptCredentialResponseJson.class);
|
||||
if (responseJson.getReceiptCredentialResponse() != null) {
|
||||
return responseJson.getReceiptCredentialResponse();
|
||||
} else {
|
||||
throw new MalformedResponseException("Unable to parse response");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public SubscriptionLevels getSubscriptionLevels() throws IOException {
|
||||
String result = makeServiceRequestWithoutAuthentication(SUBSCRIPTION_LEVELS, "GET", null);
|
||||
return JsonUtil.fromJsonResponse(result, SubscriptionLevels.class);
|
||||
|
||||
@@ -10,6 +10,7 @@ package org.whispersystems.signalservice.internal.util;
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.DeserializationContext;
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.JsonDeserializer;
|
||||
@@ -52,6 +53,22 @@ public class JsonUtil {
|
||||
return objectMapper.readValue(json, clazz);
|
||||
}
|
||||
|
||||
public static <T> T fromJson(String json, TypeReference<T> typeRef)
|
||||
throws IOException
|
||||
{
|
||||
return objectMapper.readValue(json, typeRef);
|
||||
}
|
||||
|
||||
public static <T> T fromJsonResponse(String json, TypeReference<T> typeRef)
|
||||
throws MalformedResponseException
|
||||
{
|
||||
try {
|
||||
return JsonUtil.fromJson(json, typeRef);
|
||||
} catch (IOException e) {
|
||||
throw new MalformedResponseException("Unable to parse entity", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> T fromJsonResponse(String body, Class<T> clazz)
|
||||
throws MalformedResponseException {
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user