From 8b8c6237be083827c158b90287cc21c393a2fc6a Mon Sep 17 00:00:00 2001 From: Ehren Kret Date: Thu, 14 Oct 2021 12:06:19 -0500 Subject: [PATCH] Use last subscription created at time as a subscription generation number --- .../controllers/SubscriptionController.java | 3 ++- .../textsecuregcm/stripe/StripeManager.java | 16 ++++++++++------ 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/SubscriptionController.java b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/SubscriptionController.java index 4345d9ad2..7b0ef8a2e 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/SubscriptionController.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/SubscriptionController.java @@ -305,11 +305,12 @@ public class SubscriptionController { } if (record.subscriptionId == null) { + long lastSubscriptionCreatedAt = record.subscriptionCreatedAt != null ? record.subscriptionCreatedAt.getEpochSecond() : 0; // we don't have one yet so create it and then record the subscription id // // this relies on stripe's idempotency key to avoid creating more than one subscription if the client // retries this request - return stripeManager.createSubscription(record.customerId, priceConfiguration.getId(), level) + return stripeManager.createSubscription(record.customerId, priceConfiguration.getId(), level, lastSubscriptionCreatedAt) .thenCompose(subscription -> subscriptionManager.subscriptionCreated( requestData.subscriberUser, subscription.getId(), requestData.now, level) .thenApply(unused -> subscription)); diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/stripe/StripeManager.java b/service/src/main/java/org/whispersystems/textsecuregcm/stripe/StripeManager.java index 42f590eb9..67d257ec8 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/stripe/StripeManager.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/stripe/StripeManager.java @@ -50,6 +50,7 @@ import javax.annotation.Nullable; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import org.apache.commons.codec.binary.Hex; +import org.whispersystems.textsecuregcm.util.Conversions; public class StripeManager { @@ -139,7 +140,7 @@ public class StripeManager { }, executor); } - public CompletableFuture createSubscription(String customerId, String priceId, long level) { + public CompletableFuture createSubscription(String customerId, String priceId, long level, long lastSubscriptionCreatedAt) { return CompletableFuture.supplyAsync(() -> { SubscriptionCreateParams params = SubscriptionCreateParams.builder() .setCustomer(customerId) @@ -152,9 +153,9 @@ public class StripeManager { // the idempotency key intentionally excludes priceId // // If the client tells the server several times in a row before the initial creation of a subscription to - // create a subscription, we want to ensure only one gets created. If the prices are different each time, - // whichever one gets to stripe first will win (depending on how idempotent the idempotency keys are...) - return Subscription.create(params, commonOptions(generateIdempotencyKeyForCustomerId(customerId))); + // create a subscription, we want to ensure only one gets created. + return Subscription.create(params, commonOptions(generateIdempotencyKeyForCreateSubscription( + customerId, lastSubscriptionCreatedAt))); } catch (StripeException e) { throw new CompletionException(e); } @@ -332,8 +333,11 @@ public class StripeManager { return generateIdempotencyKey("subscriberUser", mac -> mac.update(subscriberUser)); } - private String generateIdempotencyKeyForCustomerId(String customerId) { - return generateIdempotencyKey("customerId", mac -> mac.update(customerId.getBytes(StandardCharsets.UTF_8))); + private String generateIdempotencyKeyForCreateSubscription(String customerId, long lastSubscriptionCreatedAt) { + return generateIdempotencyKey("customerId", mac -> { + mac.update(customerId.getBytes(StandardCharsets.UTF_8)); + mac.update(Conversions.longToByteArray(lastSubscriptionCreatedAt)); + }); } private String generateIdempotencyKey(String type, Consumer byteConsumer) {