From 7c5724e48f2c2c4eb6071d5c53f90e52959dc5a2 Mon Sep 17 00:00:00 2001 From: Cy Okeke Date: Wed, 3 Jan 2024 16:01:05 +0100 Subject: [PATCH] refactoring continues --- .../Implementations/ChargeSucceededHandler.cs | 9 +- .../Implementations/InvoiceCreatedHandler.cs | 33 ++++++ .../PaymentMethodAttachedHandler.cs | 105 ++++++++++++++++++ .../PaymentSucceededHandler.cs | 7 +- .../SubscriptionUpdatedHandler.cs | 9 +- 5 files changed, 155 insertions(+), 8 deletions(-) create mode 100644 src/Billing/Services/Implementations/InvoiceCreatedHandler.cs create mode 100644 src/Billing/Services/Implementations/PaymentMethodAttachedHandler.cs diff --git a/src/Billing/Services/Implementations/ChargeSucceededHandler.cs b/src/Billing/Services/Implementations/ChargeSucceededHandler.cs index a73bae1832..437f8f83c9 100644 --- a/src/Billing/Services/Implementations/ChargeSucceededHandler.cs +++ b/src/Billing/Services/Implementations/ChargeSucceededHandler.cs @@ -15,16 +15,19 @@ public class ChargeSucceededHandler : StripeWebhookHandler private readonly ITransactionRepository _transactionRepository; private readonly ILogger _logger; private readonly IStripeEventService _stripeEventService; + private readonly IWebhookUtility _webhookUtility; public ChargeSucceededHandler( ITransactionRepository transactionRepository, ILogger logger, - IStripeEventService stripeEventService) + IStripeEventService stripeEventService, + IWebhookUtility webhookUtility) { _transactionRepository = transactionRepository; _logger = logger; _stripeEventService = stripeEventService; + _webhookUtility = webhookUtility; } protected override bool CanHandle(Event parsedEvent) @@ -54,7 +57,7 @@ public class ChargeSucceededHandler : StripeWebhookHandler if (invoice?.SubscriptionId != null) { subscription = await subscriptionService.GetAsync(invoice.SubscriptionId); - ids = GetIdsFromMetaData(subscription?.Metadata); + ids = _webhookUtility.GetIdsFromMetaData(subscription?.Metadata); } } @@ -68,7 +71,7 @@ public class ChargeSucceededHandler : StripeWebhookHandler { if (sub.Status != StripeSubscriptionStatus.Canceled && sub.Status != StripeSubscriptionStatus.IncompleteExpired) { - ids = GetIdsFromMetaData(sub.Metadata); + ids = _webhookUtility.GetIdsFromMetaData(sub.Metadata); if (ids.Item1.HasValue || ids.Item2.HasValue) { subscription = sub; diff --git a/src/Billing/Services/Implementations/InvoiceCreatedHandler.cs b/src/Billing/Services/Implementations/InvoiceCreatedHandler.cs new file mode 100644 index 0000000000..5e711a1615 --- /dev/null +++ b/src/Billing/Services/Implementations/InvoiceCreatedHandler.cs @@ -0,0 +1,33 @@ +using Bit.Billing.Constants; +using Microsoft.AspNetCore.Mvc; +using Stripe; + +namespace Bit.Billing.Services.Implementations; + +public class InvoiceCreatedHandler : StripeWebhookHandler +{ + private readonly IStripeEventService _stripeEventService; + private readonly IWebhookUtility _webhookUtility; + + public InvoiceCreatedHandler(IStripeEventService stripeEventService, + IWebhookUtility webhookUtility) + { + _stripeEventService = stripeEventService; + _webhookUtility = webhookUtility; + } + protected override bool CanHandle(Event parsedEvent) + { + return parsedEvent.Type.Equals(HandledStripeWebhook.InvoiceCreated); + } + + protected override async Task ProcessEvent(Event parsedEvent) + { + var invoice = await _stripeEventService.GetInvoice(parsedEvent, true); + if (!invoice.Paid && _webhookUtility.UnpaidAutoChargeInvoiceForSubscriptionCycle(invoice)) + { + await _webhookUtility.AttemptToPayInvoice(invoice); + } + + return new OkResult(); + } +} diff --git a/src/Billing/Services/Implementations/PaymentMethodAttachedHandler.cs b/src/Billing/Services/Implementations/PaymentMethodAttachedHandler.cs new file mode 100644 index 0000000000..375b12468b --- /dev/null +++ b/src/Billing/Services/Implementations/PaymentMethodAttachedHandler.cs @@ -0,0 +1,105 @@ +using Bit.Billing.Constants; +using Microsoft.AspNetCore.Mvc; +using Stripe; + +namespace Bit.Billing.Services.Implementations; + +public class PaymentMethodAttachedHandler : StripeWebhookHandler +{ + private readonly IStripeEventService _stripeEventService; + private readonly IWebhookUtility _webhookUtility; + private readonly ILogger _logger; + + public PaymentMethodAttachedHandler(IStripeEventService stripeEventService, + IWebhookUtility webhookUtility, + ILogger logger) + { + _stripeEventService = stripeEventService; + _webhookUtility = webhookUtility; + _logger = logger; + } + + protected override bool CanHandle(Event parsedEvent) + { + return parsedEvent.Type.Equals(HandledStripeWebhook.InvoiceCreated); + } + + protected override async Task ProcessEvent(Event parsedEvent) + { + var paymentMethod = await _stripeEventService.GetPaymentMethod(parsedEvent); + await HandlePaymentMethodAttachedAsync(paymentMethod); + return new OkResult(); + } + + private async Task HandlePaymentMethodAttachedAsync(PaymentMethod paymentMethod) + { + if (paymentMethod is null) + { + _logger.LogWarning("Attempted to handle the event payment_method.attached but paymentMethod was null"); + return; + } + + var subscriptionService = new SubscriptionService(); + var subscriptionListOptions = new SubscriptionListOptions + { + Customer = paymentMethod.CustomerId, + Status = StripeSubscriptionStatus.Unpaid, + Expand = new List { "data.latest_invoice" } + }; + + StripeList unpaidSubscriptions; + try + { + unpaidSubscriptions = await subscriptionService.ListAsync(subscriptionListOptions); + } + catch (Exception e) + { + _logger.LogError(e, + "Attempted to get unpaid invoices for customer {CustomerId} but encountered an error while calling Stripe", + paymentMethod.CustomerId); + + return; + } + + foreach (var unpaidSubscription in unpaidSubscriptions) + { + await AttemptToPayOpenSubscriptionAsync(unpaidSubscription); + } + } + + private async Task AttemptToPayOpenSubscriptionAsync(Subscription unpaidSubscription) + { + var latestInvoice = unpaidSubscription.LatestInvoice; + + if (unpaidSubscription.LatestInvoice is null) + { + _logger.LogWarning( + "Attempted to pay unpaid subscription {SubscriptionId} but latest invoice didn't exist", + unpaidSubscription.Id); + + return; + } + + if (latestInvoice.Status != StripeInvoiceStatus.Open) + { + _logger.LogWarning( + "Attempted to pay unpaid subscription {SubscriptionId} but latest invoice wasn't \"open\"", + unpaidSubscription.Id); + + return; + } + + try + { + await _webhookUtility.AttemptToPayInvoice(latestInvoice, true); + } + catch (Exception e) + { + _logger.LogError(e, + "Attempted to pay open invoice {InvoiceId} on unpaid subscription {SubscriptionId} but encountered an error", + latestInvoice.Id, unpaidSubscription.Id); + throw; + } + } + +} diff --git a/src/Billing/Services/Implementations/PaymentSucceededHandler.cs b/src/Billing/Services/Implementations/PaymentSucceededHandler.cs index 3264325194..cd5dada7ff 100644 --- a/src/Billing/Services/Implementations/PaymentSucceededHandler.cs +++ b/src/Billing/Services/Implementations/PaymentSucceededHandler.cs @@ -21,6 +21,7 @@ public class PaymentSucceededHandler : StripeWebhookHandler private readonly ICurrentContext _currentContext; private readonly IUserService _userService; private readonly IUserRepository _userRepository; + private readonly IWebhookUtility _webhookUtility; public PaymentSucceededHandler(IStripeEventService stripeEventService, IOrganizationService organizationService, @@ -28,7 +29,8 @@ public class PaymentSucceededHandler : StripeWebhookHandler IReferenceEventService referenceEventService, ICurrentContext currentContext, IUserService userService, - IUserRepository userRepository) + IUserRepository userRepository, + IWebhookUtility webhookUtility) { _stripeEventService = stripeEventService; _organizationService = organizationService; @@ -37,6 +39,7 @@ public class PaymentSucceededHandler : StripeWebhookHandler _currentContext = currentContext; _userService = userService; _userRepository = userRepository; + _webhookUtility = webhookUtility; } protected override bool CanHandle(Event parsedEvent) { @@ -57,7 +60,7 @@ public class PaymentSucceededHandler : StripeWebhookHandler await Task.Delay(5000); } - var ids = GetIdsFromMetaData(subscription.Metadata); + var ids = _webhookUtility.GetIdsFromMetaData(subscription.Metadata); // org if (ids.Item1.HasValue) { diff --git a/src/Billing/Services/Implementations/SubscriptionUpdatedHandler.cs b/src/Billing/Services/Implementations/SubscriptionUpdatedHandler.cs index 2aff9b0acb..fef86c537c 100644 --- a/src/Billing/Services/Implementations/SubscriptionUpdatedHandler.cs +++ b/src/Billing/Services/Implementations/SubscriptionUpdatedHandler.cs @@ -12,16 +12,19 @@ public class SubscriptionUpdatedHandler : StripeWebhookHandler private readonly IUserService _userService; private readonly IStripeEventService _stripeEventService; private readonly IOrganizationSponsorshipRenewCommand _organizationSponsorshipRenewCommand; + private readonly IWebhookUtility _webhookUtility; public SubscriptionUpdatedHandler(IOrganizationService organizationService, IUserService userService, IStripeEventService stripeEventService, - IOrganizationSponsorshipRenewCommand organizationSponsorshipRenewCommand) + IOrganizationSponsorshipRenewCommand organizationSponsorshipRenewCommand, + IWebhookUtility webhookUtility) { _organizationService = organizationService; _userService = userService; _stripeEventService = stripeEventService; _organizationSponsorshipRenewCommand = organizationSponsorshipRenewCommand; + _webhookUtility = webhookUtility; } protected override bool CanHandle(Event parsedEvent) { @@ -33,7 +36,7 @@ public class SubscriptionUpdatedHandler : StripeWebhookHandler if (parsedEvent.Type.Equals(HandledStripeWebhook.SubscriptionUpdated)) { var subscription = await _stripeEventService.GetSubscription(parsedEvent, true); - var ids = GetIdsFromMetaData(subscription.Metadata); + var ids = _webhookUtility.GetIdsFromMetaData(subscription.Metadata); var organizationId = ids.Item1 ?? Guid.Empty; var userId = ids.Item2 ?? Guid.Empty; var subActive = subscription.Status == StripeSubscriptionStatus.Active; @@ -51,7 +54,7 @@ public class SubscriptionUpdatedHandler : StripeWebhookHandler await _organizationService.UpdateExpirationDateAsync(organizationId, subscription.CurrentPeriodEnd); - if (IsSponsoredSubscription(subscription)) + if (_webhookUtility.IsSponsoredSubscription(subscription)) { await _organizationSponsorshipRenewCommand.UpdateExpirationDateAsync(organizationId, subscription.CurrentPeriodEnd);