Persist onetime donation payment success timestamps for Braintree transactions

This commit is contained in:
Katherine 2023-12-14 19:48:29 -05:00 committed by GitHub
parent bb347999ce
commit 9cfc2ba09a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 12 additions and 15 deletions

View File

@ -780,8 +780,9 @@ public class SubscriptionController {
}) })
.thenCompose(unused -> braintreeManager.captureOneTimePayment(request.payerId, request.paymentId, .thenCompose(unused -> braintreeManager.captureOneTimePayment(request.payerId, request.paymentId,
request.paymentToken, request.currency, request.amount, request.level)) request.paymentToken, request.currency, request.amount, request.level))
.thenApply(chargeSuccessDetails -> Response.ok( .thenCompose(chargeSuccessDetails -> oneTimeDonationsManager.putPaidAt(chargeSuccessDetails.paymentId(), Instant.now()))
new ConfirmPayPalBoostResponse(chargeSuccessDetails.paymentId())).build()); .thenApply(paymentId -> Response.ok(
new ConfirmPayPalBoostResponse(paymentId)).build());
} }
public static class CreateBoostReceiptCredentialsRequest { public static class CreateBoostReceiptCredentialsRequest {

View File

@ -7,7 +7,6 @@ package org.whispersystems.textsecuregcm.storage;
import static com.codahale.metrics.MetricRegistry.name; import static com.codahale.metrics.MetricRegistry.name;
import com.google.common.annotations.VisibleForTesting;
import io.micrometer.core.instrument.Metrics; import io.micrometer.core.instrument.Metrics;
import java.time.Instant; import java.time.Instant;
import java.util.Map; import java.util.Map;
@ -15,13 +14,12 @@ import java.util.Objects;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import org.whispersystems.textsecuregcm.util.AttributeValues; 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.DynamoDbAsyncClient;
import software.amazon.awssdk.services.dynamodb.model.GetItemRequest; import software.amazon.awssdk.services.dynamodb.model.GetItemRequest;
import software.amazon.awssdk.services.dynamodb.model.PutItemRequest; import software.amazon.awssdk.services.dynamodb.model.PutItemRequest;
public class OneTimeDonationsManager { public class OneTimeDonationsManager {
public static final String KEY_PAYMENT_INTENT_ID = "P"; // S public static final String KEY_PAYMENT_ID = "P"; // S
public static final String ATTR_PAID_AT = "A"; // N public static final String ATTR_PAID_AT = "A"; // N
private static final String ONETIME_DONATION_NOT_FOUND_COUNTER_NAME = name(OneTimeDonationsManager.class, "onetimeDonationNotFound"); private static final String ONETIME_DONATION_NOT_FOUND_COUNTER_NAME = name(OneTimeDonationsManager.class, "onetimeDonationNotFound");
private final String table; private final String table;
@ -34,11 +32,11 @@ public class OneTimeDonationsManager {
this.dynamoDbAsyncClient = Objects.requireNonNull(dynamoDbAsyncClient); this.dynamoDbAsyncClient = Objects.requireNonNull(dynamoDbAsyncClient);
} }
public CompletableFuture<Instant> getPaidAt(final String paymentIntentId, final Instant fallbackTimestamp) { public CompletableFuture<Instant> getPaidAt(final String paymentId, final Instant fallbackTimestamp) {
final GetItemRequest getItemRequest = GetItemRequest.builder() final GetItemRequest getItemRequest = GetItemRequest.builder()
.consistentRead(Boolean.TRUE) .consistentRead(Boolean.TRUE)
.tableName(table) .tableName(table)
.key(Map.of(KEY_PAYMENT_INTENT_ID, AttributeValues.fromString(paymentIntentId))) .key(Map.of(KEY_PAYMENT_ID, AttributeValues.fromString(paymentId)))
.projectionExpression(ATTR_PAID_AT) .projectionExpression(ATTR_PAID_AT)
.build(); .build();
@ -52,15 +50,13 @@ public class OneTimeDonationsManager {
}); });
} }
@VisibleForTesting public CompletableFuture<String> putPaidAt(final String paymentId, final Instant paidAt) {
CompletableFuture<Void> putPaidAt(final String paymentIntentId, final Instant paidAt) {
return dynamoDbAsyncClient.putItem(PutItemRequest.builder() return dynamoDbAsyncClient.putItem(PutItemRequest.builder()
.tableName(table) .tableName(table)
.item(Map.of( .item(Map.of(
KEY_PAYMENT_INTENT_ID, AttributeValues.fromString(paymentIntentId), KEY_PAYMENT_ID, AttributeValues.fromString(paymentId),
ATTR_PAID_AT, AttributeValues.fromLong(paidAt.getEpochSecond()))) ATTR_PAID_AT, AttributeValues.fromLong(paidAt.getEpochSecond())))
.build()) .build())
.thenRun(Util.NOOP); .thenApply(unused -> paymentId);
} }
} }

View File

@ -243,10 +243,10 @@ public final class DynamoDbExtensionSchema {
.build())), .build())),
ONETIME_DONATIONS("onetime_donations_test", ONETIME_DONATIONS("onetime_donations_test",
OneTimeDonationsManager.KEY_PAYMENT_INTENT_ID, OneTimeDonationsManager.KEY_PAYMENT_ID,
null, null,
List.of(AttributeDefinition.builder() List.of(AttributeDefinition.builder()
.attributeName(OneTimeDonationsManager.KEY_PAYMENT_INTENT_ID) .attributeName(OneTimeDonationsManager.KEY_PAYMENT_ID)
.attributeType(ScalarAttributeType.S) .attributeType(ScalarAttributeType.S)
.build()), .build()),
List.of(), List.of()), List.of(), List.of()),

View File

@ -25,7 +25,7 @@ public class OnetimeDonationsManagerTest {
} }
@Test @Test
void testGetPaidAtTimestamp() { void testSetGetPaidAtTimestamp() {
final String validPaymentIntentId = "abc"; final String validPaymentIntentId = "abc";
final Instant paidAt = Instant.ofEpochSecond(1_000_000); final Instant paidAt = Instant.ofEpochSecond(1_000_000);
final Instant fallBackTimestamp = Instant.ofEpochSecond(2_000_000); final Instant fallBackTimestamp = Instant.ofEpochSecond(2_000_000);