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 5350186bd..62cd0f8ed 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/SubscriptionController.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/SubscriptionController.java @@ -35,6 +35,7 @@ import javax.annotation.Nonnull; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import javax.validation.Valid; +import javax.validation.constraints.Min; import javax.validation.constraints.NotEmpty; import javax.ws.rs.BadRequestException; import javax.ws.rs.Consumes; @@ -409,6 +410,56 @@ public class SubscriptionController { Collectors.toMap(entry -> entry.getKey().toUpperCase(Locale.ROOT), Function.identity()))).build()); } + public static class CreateBoostRequest { + + private final String currency; + private final long amount; + + @JsonCreator + public CreateBoostRequest( + @JsonProperty("currency") String currency, + @JsonProperty("amount") long amount) { + this.currency = currency; + this.amount = amount; + } + + @NotEmpty + @ExactlySize(3) + public String getCurrency() { + return currency; + } + + @Min(1) + public long getAmount() { + return amount; + } + } + + public static class CreateBoostResponse { + + private final String clientSecret; + + @JsonCreator + public CreateBoostResponse( + @JsonProperty("clientSecret") String clientSecret) { + this.clientSecret = clientSecret; + } + + public String getClientSecret() { + return clientSecret; + } + } + + @Timed + @POST + @Path("/boost/create") + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public CompletableFuture createBoostPaymentIntent(CreateBoostRequest request) { + return stripeManager.createPaymentIntent(request.getCurrency(), request.getAmount()) + .thenApply(paymentIntent -> Response.ok(new CreateBoostResponse(paymentIntent.getClientSecret())).build()); + } + public static class GetSubscriptionInformationResponse { public static class 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 67d257ec8..c93fab153 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/stripe/StripeManager.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/stripe/StripeManager.java @@ -11,6 +11,7 @@ import com.stripe.exception.StripeException; import com.stripe.model.Customer; import com.stripe.model.Invoice; import com.stripe.model.InvoiceLineItem; +import com.stripe.model.PaymentIntent; import com.stripe.model.Price; import com.stripe.model.Product; import com.stripe.model.SetupIntent; @@ -22,6 +23,7 @@ import com.stripe.param.CustomerRetrieveParams; import com.stripe.param.CustomerUpdateParams; import com.stripe.param.CustomerUpdateParams.InvoiceSettings; import com.stripe.param.InvoiceListParams; +import com.stripe.param.PaymentIntentCreateParams; import com.stripe.param.PriceRetrieveParams; import com.stripe.param.SetupIntentCreateParams; import com.stripe.param.SubscriptionCancelParams; @@ -40,6 +42,7 @@ import java.util.Base64; import java.util.Collection; import java.util.Comparator; import java.util.List; +import java.util.Locale; import java.util.Objects; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; @@ -140,6 +143,20 @@ public class StripeManager { }, executor); } + public CompletableFuture createPaymentIntent(String currency, long amount) { + return CompletableFuture.supplyAsync(() -> { + PaymentIntentCreateParams params = PaymentIntentCreateParams.builder() + .setAmount(amount) + .setCurrency(currency.toLowerCase(Locale.ROOT)) + .build(); + try { + return PaymentIntent.create(params, commonOptions()); + } catch (StripeException e) { + throw new CompletionException(e); + } + }, executor); + } + public CompletableFuture createSubscription(String customerId, String priceId, long level, long lastSubscriptionCreatedAt) { return CompletableFuture.supplyAsync(() -> { SubscriptionCreateParams params = SubscriptionCreateParams.builder()