mirror of
https://github.com/signalapp/Signal-Server
synced 2026-04-20 18:08:06 +01:00
Calculate onetime badge expiration from payment success timestamp
This commit is contained in:
@@ -187,6 +187,7 @@ import org.whispersystems.textsecuregcm.storage.KeysManager;
|
||||
import org.whispersystems.textsecuregcm.storage.MessagesCache;
|
||||
import org.whispersystems.textsecuregcm.storage.MessagesDynamoDb;
|
||||
import org.whispersystems.textsecuregcm.storage.MessagesManager;
|
||||
import org.whispersystems.textsecuregcm.storage.OneTimeDonationsManager;
|
||||
import org.whispersystems.textsecuregcm.storage.PhoneNumberIdentifiers;
|
||||
import org.whispersystems.textsecuregcm.storage.Profiles;
|
||||
import org.whispersystems.textsecuregcm.storage.ProfilesManager;
|
||||
@@ -547,6 +548,8 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
|
||||
config.getDynamoDbTables().getIssuedReceipts().getExpiration(),
|
||||
dynamoDbAsyncClient,
|
||||
config.getDynamoDbTables().getIssuedReceipts().getGenerator());
|
||||
OneTimeDonationsManager oneTimeDonationsManager = new OneTimeDonationsManager(
|
||||
config.getDynamoDbTables().getOnetimeDonations().getTableName(), dynamoDbAsyncClient);
|
||||
RedeemedReceiptsManager redeemedReceiptsManager = new RedeemedReceiptsManager(clock,
|
||||
config.getDynamoDbTables().getRedeemedReceipts().getTableName(),
|
||||
dynamoDbAsyncClient,
|
||||
@@ -832,8 +835,8 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
|
||||
);
|
||||
if (config.getSubscription() != null && config.getOneTimeDonations() != null) {
|
||||
commonControllers.add(new SubscriptionController(clock, config.getSubscription(), config.getOneTimeDonations(),
|
||||
subscriptionManager, stripeManager, braintreeManager, zkReceiptOperations, issuedReceiptsManager, profileBadgeConverter,
|
||||
resourceBundleLevelTranslator, bankMandateTranslator));
|
||||
subscriptionManager, stripeManager, braintreeManager, zkReceiptOperations, issuedReceiptsManager, oneTimeDonationsManager,
|
||||
profileBadgeConverter, resourceBundleLevelTranslator, bankMandateTranslator));
|
||||
}
|
||||
|
||||
for (Object controller : commonControllers) {
|
||||
|
||||
@@ -59,6 +59,7 @@ public class DynamoDbTables {
|
||||
private final Table kemKeys;
|
||||
private final Table kemLastResortKeys;
|
||||
private final TableWithExpiration messages;
|
||||
private final Table onetimeDonations;
|
||||
private final Table phoneNumberIdentifiers;
|
||||
private final Table profiles;
|
||||
private final Table pushChallenge;
|
||||
@@ -82,6 +83,7 @@ public class DynamoDbTables {
|
||||
@JsonProperty("pqKeys") final Table kemKeys,
|
||||
@JsonProperty("pqLastResortKeys") final Table kemLastResortKeys,
|
||||
@JsonProperty("messages") final TableWithExpiration messages,
|
||||
@JsonProperty("onetimeDonations") final Table onetimeDonations,
|
||||
@JsonProperty("phoneNumberIdentifiers") final Table phoneNumberIdentifiers,
|
||||
@JsonProperty("profiles") final Table profiles,
|
||||
@JsonProperty("pushChallenge") final Table pushChallenge,
|
||||
@@ -104,6 +106,7 @@ public class DynamoDbTables {
|
||||
this.kemKeys = kemKeys;
|
||||
this.kemLastResortKeys = kemLastResortKeys;
|
||||
this.messages = messages;
|
||||
this.onetimeDonations = onetimeDonations;
|
||||
this.phoneNumberIdentifiers = phoneNumberIdentifiers;
|
||||
this.profiles = profiles;
|
||||
this.pushChallenge = pushChallenge;
|
||||
@@ -187,6 +190,12 @@ public class DynamoDbTables {
|
||||
return messages;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Valid
|
||||
public Table getOnetimeDonations() {
|
||||
return onetimeDonations;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Valid
|
||||
public Table getPhoneNumberIdentifiers() {
|
||||
|
||||
@@ -85,6 +85,7 @@ import org.whispersystems.textsecuregcm.entities.PurchasableBadge;
|
||||
import org.whispersystems.textsecuregcm.metrics.MetricsUtil;
|
||||
import org.whispersystems.textsecuregcm.metrics.UserAgentTagUtil;
|
||||
import org.whispersystems.textsecuregcm.storage.IssuedReceiptsManager;
|
||||
import org.whispersystems.textsecuregcm.storage.OneTimeDonationsManager;
|
||||
import org.whispersystems.textsecuregcm.storage.SubscriptionManager;
|
||||
import org.whispersystems.textsecuregcm.storage.SubscriptionManager.GetResult;
|
||||
import org.whispersystems.textsecuregcm.subscriptions.BankMandateTranslator;
|
||||
@@ -117,6 +118,7 @@ public class SubscriptionController {
|
||||
private final BraintreeManager braintreeManager;
|
||||
private final ServerZkReceiptOperations zkReceiptOperations;
|
||||
private final IssuedReceiptsManager issuedReceiptsManager;
|
||||
private final OneTimeDonationsManager oneTimeDonationsManager;
|
||||
private final BadgeTranslator badgeTranslator;
|
||||
private final LevelTranslator levelTranslator;
|
||||
private final BankMandateTranslator bankMandateTranslator;
|
||||
@@ -137,6 +139,7 @@ public class SubscriptionController {
|
||||
@Nonnull BraintreeManager braintreeManager,
|
||||
@Nonnull ServerZkReceiptOperations zkReceiptOperations,
|
||||
@Nonnull IssuedReceiptsManager issuedReceiptsManager,
|
||||
@Nonnull OneTimeDonationsManager oneTimeDonationsManager,
|
||||
@Nonnull BadgeTranslator badgeTranslator,
|
||||
@Nonnull LevelTranslator levelTranslator,
|
||||
@Nonnull BankMandateTranslator bankMandateTranslator) {
|
||||
@@ -148,6 +151,7 @@ public class SubscriptionController {
|
||||
this.braintreeManager = Objects.requireNonNull(braintreeManager);
|
||||
this.zkReceiptOperations = Objects.requireNonNull(zkReceiptOperations);
|
||||
this.issuedReceiptsManager = Objects.requireNonNull(issuedReceiptsManager);
|
||||
this.oneTimeDonationsManager = Objects.requireNonNull(oneTimeDonationsManager);
|
||||
this.badgeTranslator = Objects.requireNonNull(badgeTranslator);
|
||||
this.levelTranslator = Objects.requireNonNull(levelTranslator);
|
||||
this.bankMandateTranslator = Objects.requireNonNull(bankMandateTranslator);
|
||||
@@ -852,8 +856,9 @@ public class SubscriptionController {
|
||||
final long finalLevel = level;
|
||||
return issuedReceiptsManager.recordIssuance(paymentDetails.id(), manager.getProcessor(),
|
||||
receiptCredentialRequest, clock.instant())
|
||||
.thenApply(unused -> {
|
||||
Instant expiration = paymentDetails.created()
|
||||
.thenCompose(unused -> oneTimeDonationsManager.getPaidAt(paymentDetails.id(), paymentDetails.created()))
|
||||
.thenApply(paidAt -> {
|
||||
Instant expiration = paidAt
|
||||
.plus(levelExpiration)
|
||||
.truncatedTo(ChronoUnit.DAYS)
|
||||
.plus(1, ChronoUnit.DAYS);
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.storage;
|
||||
|
||||
import static com.codahale.metrics.MetricRegistry.name;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import io.micrometer.core.instrument.Metrics;
|
||||
import java.time.Instant;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import javax.annotation.Nonnull;
|
||||
import org.whispersystems.textsecuregcm.util.AttributeValues;
|
||||
import org.whispersystems.textsecuregcm.util.Util;
|
||||
import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient;
|
||||
import software.amazon.awssdk.services.dynamodb.model.GetItemRequest;
|
||||
import software.amazon.awssdk.services.dynamodb.model.PutItemRequest;
|
||||
|
||||
public class OneTimeDonationsManager {
|
||||
public static final String KEY_PAYMENT_INTENT_ID = "P"; // S
|
||||
public static final String ATTR_PAID_AT = "A"; // N
|
||||
private static final String ONETIME_DONATION_NOT_FOUND_COUNTER_NAME = name(OneTimeDonationsManager.class, "onetimeDonationNotFound");
|
||||
private final String table;
|
||||
private final DynamoDbAsyncClient dynamoDbAsyncClient;
|
||||
|
||||
public OneTimeDonationsManager(
|
||||
@Nonnull String table,
|
||||
@Nonnull DynamoDbAsyncClient dynamoDbAsyncClient) {
|
||||
this.table = Objects.requireNonNull(table);
|
||||
this.dynamoDbAsyncClient = Objects.requireNonNull(dynamoDbAsyncClient);
|
||||
}
|
||||
|
||||
public CompletableFuture<Instant> getPaidAt(final String paymentIntentId, final Instant fallbackTimestamp) {
|
||||
final GetItemRequest getItemRequest = GetItemRequest.builder()
|
||||
.consistentRead(Boolean.TRUE)
|
||||
.tableName(table)
|
||||
.key(Map.of(KEY_PAYMENT_INTENT_ID, AttributeValues.fromString(paymentIntentId)))
|
||||
.projectionExpression(ATTR_PAID_AT)
|
||||
.build();
|
||||
|
||||
return dynamoDbAsyncClient.getItem(getItemRequest).thenApply(getItemResponse -> {
|
||||
if (!getItemResponse.hasItem()) {
|
||||
Metrics.counter(ONETIME_DONATION_NOT_FOUND_COUNTER_NAME).increment();
|
||||
return fallbackTimestamp;
|
||||
}
|
||||
|
||||
return Instant.ofEpochSecond(AttributeValues.getLong(getItemResponse.item(), ATTR_PAID_AT, fallbackTimestamp.getEpochSecond()));
|
||||
});
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
CompletableFuture<Void> putPaidAt(final String paymentIntentId, final Instant paidAt) {
|
||||
return dynamoDbAsyncClient.putItem(PutItemRequest.builder()
|
||||
.tableName(table)
|
||||
.item(Map.of(
|
||||
KEY_PAYMENT_INTENT_ID, AttributeValues.fromString(paymentIntentId),
|
||||
ATTR_PAID_AT, AttributeValues.fromLong(paidAt.getEpochSecond())))
|
||||
.build())
|
||||
.thenRun(Util.NOOP);
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user