Add `paymentMethod` and `paymentProcessing` fields to `GET /v1/subscription/{subscriberId}` endpoint
This commit is contained in:
		
							parent
							
								
									e1aa734c40
								
							
						
					
					
						commit
						207ae6129b
					
				|  | @ -394,6 +394,7 @@ public class SubscriptionController { | ||||||
|     return switch (paymentMethod) { |     return switch (paymentMethod) { | ||||||
|       case CARD, SEPA_DEBIT -> stripeManager; |       case CARD, SEPA_DEBIT -> stripeManager; | ||||||
|       case PAYPAL -> braintreeManager; |       case PAYPAL -> braintreeManager; | ||||||
|  |       case UNKNOWN -> throw new BadRequestException("Invalid payment method"); | ||||||
|     }; |     }; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  | @ -886,7 +887,7 @@ public class SubscriptionController { | ||||||
| 
 | 
 | ||||||
|       public record Subscription(long level, Instant billingCycleAnchor, Instant endOfCurrentPeriod, boolean active, |       public record Subscription(long level, Instant billingCycleAnchor, Instant endOfCurrentPeriod, boolean active, | ||||||
|                                  boolean cancelAtPeriodEnd, String currency, BigDecimal amount, String status, |                                  boolean cancelAtPeriodEnd, String currency, BigDecimal amount, String status, | ||||||
|                                  SubscriptionProcessor processor) { |                                  SubscriptionProcessor processor, PaymentMethod paymentMethod, boolean paymentProcessing) { | ||||||
| 
 | 
 | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|  | @ -919,7 +920,9 @@ public class SubscriptionController { | ||||||
|                             subscriptionInformation.price().currency(), |                             subscriptionInformation.price().currency(), | ||||||
|                             subscriptionInformation.price().amount(), |                             subscriptionInformation.price().amount(), | ||||||
|                             subscriptionInformation.status().getApiValue(), |                             subscriptionInformation.status().getApiValue(), | ||||||
|                             manager.getProcessor()), |                             manager.getProcessor(), | ||||||
|  |                             subscriptionInformation.paymentMethod(), | ||||||
|  |                             subscriptionInformation.paymentProcessing()), | ||||||
|                         subscriptionInformation.chargeFailure() |                         subscriptionInformation.chargeFailure() | ||||||
|                     )).build())); |                     )).build())); | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
|  | @ -426,12 +426,17 @@ public class BraintreeManager implements SubscriptionProcessorManager { | ||||||
|       final Instant anchor = subscription.getFirstBillingDate().toInstant(); |       final Instant anchor = subscription.getFirstBillingDate().toInstant(); | ||||||
|       final Instant endOfCurrentPeriod = subscription.getBillingPeriodEndDate().toInstant(); |       final Instant endOfCurrentPeriod = subscription.getBillingPeriodEndDate().toInstant(); | ||||||
| 
 | 
 | ||||||
|       final ChargeFailure chargeFailure = getLatestTransactionForSubscription(subscription).map(transaction -> { |       boolean paymentProcessing = false; | ||||||
|         if (getPaymentStatus(transaction.getStatus()).equals(PaymentStatus.SUCCEEDED)) { |       ChargeFailure chargeFailure = null; | ||||||
|           return null; | 
 | ||||||
|  |       final Optional<Transaction> latestTransaction = getLatestTransactionForSubscription(subscription); | ||||||
|  | 
 | ||||||
|  |       if (latestTransaction.isPresent()){ | ||||||
|  |         paymentProcessing = isPaymentProcessing(latestTransaction.get().getStatus()); | ||||||
|  |         if (!getPaymentStatus(latestTransaction.get().getStatus()).equals(PaymentStatus.SUCCEEDED)) { | ||||||
|  |           chargeFailure = createChargeFailure(latestTransaction.get()); | ||||||
|  |         } | ||||||
|       } |       } | ||||||
|         return createChargeFailure(transaction); |  | ||||||
|       }).orElse(null); |  | ||||||
| 
 | 
 | ||||||
|       return new SubscriptionInformation( |       return new SubscriptionInformation( | ||||||
|           new SubscriptionPrice(plan.getCurrencyIsoCode().toUpperCase(Locale.ROOT), |           new SubscriptionPrice(plan.getCurrencyIsoCode().toUpperCase(Locale.ROOT), | ||||||
|  | @ -442,11 +447,25 @@ public class BraintreeManager implements SubscriptionProcessorManager { | ||||||
|           Subscription.Status.ACTIVE == subscription.getStatus(), |           Subscription.Status.ACTIVE == subscription.getStatus(), | ||||||
|           !subscription.neverExpires(), |           !subscription.neverExpires(), | ||||||
|           getSubscriptionStatus(subscription.getStatus()), |           getSubscriptionStatus(subscription.getStatus()), | ||||||
|  |           latestTransaction.map(this::getPaymentMethodFromTransaction).orElse(PaymentMethod.PAYPAL), | ||||||
|  |           paymentProcessing, | ||||||
|           chargeFailure |           chargeFailure | ||||||
|       ); |       ); | ||||||
|     }, executor); |     }, executor); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   private PaymentMethod getPaymentMethodFromTransaction(Transaction transaction) { | ||||||
|  |     if (transaction.getPayPalDetails() != null) { | ||||||
|  |       return PaymentMethod.PAYPAL; | ||||||
|  |     } | ||||||
|  |     logger.error("Unexpected payment method from Braintree: {}, transaction id {}", transaction.getPaymentInstrumentType(), transaction.getId()); | ||||||
|  |     return PaymentMethod.UNKNOWN; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   private static boolean isPaymentProcessing(final Transaction.Status status) { | ||||||
|  |     return status == Transaction.Status.SETTLEMENT_PENDING; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   private ChargeFailure createChargeFailure(Transaction transaction) { |   private ChargeFailure createChargeFailure(Transaction transaction) { | ||||||
| 
 | 
 | ||||||
|     final String code; |     final String code; | ||||||
|  |  | ||||||
|  | @ -6,6 +6,7 @@ | ||||||
| package org.whispersystems.textsecuregcm.subscriptions; | package org.whispersystems.textsecuregcm.subscriptions; | ||||||
| 
 | 
 | ||||||
| public enum PaymentMethod { | public enum PaymentMethod { | ||||||
|  |   UNKNOWN, | ||||||
|   /** |   /** | ||||||
|    * A credit card or debit card, including those from Apple Pay and Google Pay |    * A credit card or debit card, including those from Apple Pay and Google Pay | ||||||
|    */ |    */ | ||||||
|  |  | ||||||
|  | @ -67,10 +67,12 @@ import javax.ws.rs.WebApplicationException; | ||||||
| import javax.ws.rs.core.Response; | import javax.ws.rs.core.Response; | ||||||
| import javax.ws.rs.core.Response.Status; | import javax.ws.rs.core.Response.Status; | ||||||
| import org.apache.commons.lang3.StringUtils; | import org.apache.commons.lang3.StringUtils; | ||||||
|  | import org.slf4j.Logger; | ||||||
|  | import org.slf4j.LoggerFactory; | ||||||
| import org.whispersystems.textsecuregcm.util.Conversions; | import org.whispersystems.textsecuregcm.util.Conversions; | ||||||
| 
 | 
 | ||||||
| public class StripeManager implements SubscriptionProcessorManager { | public class StripeManager implements SubscriptionProcessorManager { | ||||||
| 
 |   private static final Logger logger = LoggerFactory.getLogger(StripeManager.class); | ||||||
|   private static final String METADATA_KEY_LEVEL = "level"; |   private static final String METADATA_KEY_LEVEL = "level"; | ||||||
| 
 | 
 | ||||||
|   private final StripeClient stripeClient; |   private final StripeClient stripeClient; | ||||||
|  | @ -483,10 +485,16 @@ public class StripeManager implements SubscriptionProcessorManager { | ||||||
|     return getPriceForSubscription(subscription).thenCompose(price -> |     return getPriceForSubscription(subscription).thenCompose(price -> | ||||||
|             getLevelForPrice(price).thenApply(level -> { |             getLevelForPrice(price).thenApply(level -> { | ||||||
|               ChargeFailure chargeFailure = null; |               ChargeFailure chargeFailure = null; | ||||||
|  |               boolean paymentProcessing = false; | ||||||
|  |               PaymentMethod paymentMethod = null; | ||||||
| 
 | 
 | ||||||
|               if (subscription.getLatestInvoiceObject() != null && subscription.getLatestInvoiceObject().getChargeObject() != null && |               if (subscription.getLatestInvoiceObject() != null) { | ||||||
|                       (subscription.getLatestInvoiceObject().getChargeObject().getFailureCode() != null || subscription.getLatestInvoiceObject().getChargeObject().getFailureMessage() != null)) { |                 final Invoice invoice = subscription.getLatestInvoiceObject(); | ||||||
|                 Charge charge = subscription.getLatestInvoiceObject().getChargeObject(); |                 paymentProcessing = "open".equals(invoice.getStatus()); | ||||||
|  | 
 | ||||||
|  |                 if (invoice.getChargeObject() != null) { | ||||||
|  |                   final Charge charge = invoice.getChargeObject(); | ||||||
|  |                   if (charge.getFailureCode() != null || charge.getFailureMessage() != null) { | ||||||
|                     Charge.Outcome outcome = charge.getOutcome(); |                     Charge.Outcome outcome = charge.getOutcome(); | ||||||
|                     chargeFailure = new ChargeFailure( |                     chargeFailure = new ChargeFailure( | ||||||
|                         charge.getFailureCode(), |                         charge.getFailureCode(), | ||||||
|  | @ -496,6 +504,13 @@ public class StripeManager implements SubscriptionProcessorManager { | ||||||
|                         outcome != null ? outcome.getType() : null); |                         outcome != null ? outcome.getType() : null); | ||||||
|                   } |                   } | ||||||
| 
 | 
 | ||||||
|  |                   if (charge.getPaymentMethodDetails() != null | ||||||
|  |                       && charge.getPaymentMethodDetails().getType() != null) { | ||||||
|  |                     paymentMethod = getPaymentMethodFromStripeString(charge.getPaymentMethodDetails().getType(), invoice.getId()); | ||||||
|  |                   } | ||||||
|  |                 } | ||||||
|  |               } | ||||||
|  | 
 | ||||||
|               return new SubscriptionInformation( |               return new SubscriptionInformation( | ||||||
|                   new SubscriptionPrice(price.getCurrency().toUpperCase(Locale.ROOT), price.getUnitAmountDecimal()), |                   new SubscriptionPrice(price.getCurrency().toUpperCase(Locale.ROOT), price.getUnitAmountDecimal()), | ||||||
|                   level, |                   level, | ||||||
|  | @ -504,11 +519,24 @@ public class StripeManager implements SubscriptionProcessorManager { | ||||||
|                   Objects.equals(subscription.getStatus(), "active"), |                   Objects.equals(subscription.getStatus(), "active"), | ||||||
|                   subscription.getCancelAtPeriodEnd(), |                   subscription.getCancelAtPeriodEnd(), | ||||||
|                   getSubscriptionStatus(subscription.getStatus()), |                   getSubscriptionStatus(subscription.getStatus()), | ||||||
|  |                   paymentMethod, | ||||||
|  |                   paymentProcessing, | ||||||
|                   chargeFailure |                   chargeFailure | ||||||
|               ); |               ); | ||||||
|             })); |             })); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   private static PaymentMethod getPaymentMethodFromStripeString(final String paymentMethodString, final String invoiceId) { | ||||||
|  |     return switch (paymentMethodString) { | ||||||
|  |       case "sepa_debit" -> PaymentMethod.SEPA_DEBIT; | ||||||
|  |       case "card" -> PaymentMethod.CARD; | ||||||
|  |       default -> { | ||||||
|  |         logger.error("Unexpected payment method from Stripe: {}, invoice id: {}", paymentMethodString, invoiceId); | ||||||
|  |         yield PaymentMethod.UNKNOWN; | ||||||
|  |       } | ||||||
|  |     }; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   private Subscription getSubscription(Object subscriptionObj) { |   private Subscription getSubscription(Object subscriptionObj) { | ||||||
|     if (!(subscriptionObj instanceof final Subscription subscription)) { |     if (!(subscriptionObj instanceof final Subscription subscription)) { | ||||||
|       throw new IllegalArgumentException("invalid subscription object: " + subscriptionObj.getClass().getName()); |       throw new IllegalArgumentException("invalid subscription object: " + subscriptionObj.getClass().getName()); | ||||||
|  |  | ||||||
|  | @ -144,8 +144,8 @@ public interface SubscriptionProcessorManager { | ||||||
| 
 | 
 | ||||||
|   record SubscriptionInformation(SubscriptionPrice price, long level, Instant billingCycleAnchor, |   record SubscriptionInformation(SubscriptionPrice price, long level, Instant billingCycleAnchor, | ||||||
|                                  Instant endOfCurrentPeriod, boolean active, boolean cancelAtPeriodEnd, |                                  Instant endOfCurrentPeriod, boolean active, boolean cancelAtPeriodEnd, | ||||||
|                                  SubscriptionStatus status, |                                  SubscriptionStatus status, PaymentMethod paymentMethod, boolean paymentProcessing, | ||||||
|                                  ChargeFailure chargeFailure) { |                                  @Nullable ChargeFailure chargeFailure) { | ||||||
| 
 | 
 | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue
	
	 Katherine
						Katherine