1
0
mirror of https://github.com/bitwarden/server.git synced 2025-07-02 16:42:50 -05:00

[AC-1693] Send InvoiceUpcoming Notification to Client Owners (#3319)

* Add Organization_ReadOwnerEmailAddresses SPROC

* Add IOrganizationRepository.GetOwnerEmailAddressesById

* Add SendInvoiceUpcoming overload for multiple emails

* Update InvoiceUpcoming handler to send multiple emails

* Cy's feedback

* Updates from testing

Hardened against missing entity IDs in Stripe events in the StripeEventService. Updated ValidateCloudRegion to not use a refresh/expansion for the customer because the invoice.upcoming event does not have an invoice.Id. Updated the StripeController's handling of invoice.upcoming to not use a refresh/expansion for the subscription because the invoice does not have an ID.

* Fix broken test
This commit is contained in:
Alex Morask
2023-10-23 13:46:29 -04:00
committed by GitHub
parent 18b43130e8
commit c442bae2bc
10 changed files with 190 additions and 39 deletions

View File

@ -7,13 +7,16 @@ namespace Bit.Billing.Services.Implementations;
public class StripeEventService : IStripeEventService
{
private readonly GlobalSettings _globalSettings;
private readonly ILogger<StripeEventService> _logger;
private readonly IStripeFacade _stripeFacade;
public StripeEventService(
GlobalSettings globalSettings,
ILogger<StripeEventService> logger,
IStripeFacade stripeFacade)
{
_globalSettings = globalSettings;
_logger = logger;
_stripeFacade = stripeFacade;
}
@ -26,6 +29,12 @@ public class StripeEventService : IStripeEventService
return eventCharge;
}
if (string.IsNullOrEmpty(eventCharge.Id))
{
_logger.LogWarning("Cannot retrieve up-to-date Charge for Event with ID '{eventId}' because no Charge ID was included in the Event.", stripeEvent.Id);
return eventCharge;
}
var charge = await _stripeFacade.GetCharge(eventCharge.Id, new ChargeGetOptions { Expand = expand });
if (charge == null)
@ -46,6 +55,12 @@ public class StripeEventService : IStripeEventService
return eventCustomer;
}
if (string.IsNullOrEmpty(eventCustomer.Id))
{
_logger.LogWarning("Cannot retrieve up-to-date Customer for Event with ID '{eventId}' because no Customer ID was included in the Event.", stripeEvent.Id);
return eventCustomer;
}
var customer = await _stripeFacade.GetCustomer(eventCustomer.Id, new CustomerGetOptions { Expand = expand });
if (customer == null)
@ -66,6 +81,12 @@ public class StripeEventService : IStripeEventService
return eventInvoice;
}
if (string.IsNullOrEmpty(eventInvoice.Id))
{
_logger.LogWarning("Cannot retrieve up-to-date Invoice for Event with ID '{eventId}' because no Invoice ID was included in the Event.", stripeEvent.Id);
return eventInvoice;
}
var invoice = await _stripeFacade.GetInvoice(eventInvoice.Id, new InvoiceGetOptions { Expand = expand });
if (invoice == null)
@ -86,6 +107,12 @@ public class StripeEventService : IStripeEventService
return eventPaymentMethod;
}
if (string.IsNullOrEmpty(eventPaymentMethod.Id))
{
_logger.LogWarning("Cannot retrieve up-to-date Payment Method for Event with ID '{eventId}' because no Payment Method ID was included in the Event.", stripeEvent.Id);
return eventPaymentMethod;
}
var paymentMethod = await _stripeFacade.GetPaymentMethod(eventPaymentMethod.Id, new PaymentMethodGetOptions { Expand = expand });
if (paymentMethod == null)
@ -106,6 +133,12 @@ public class StripeEventService : IStripeEventService
return eventSubscription;
}
if (string.IsNullOrEmpty(eventSubscription.Id))
{
_logger.LogWarning("Cannot retrieve up-to-date Subscription for Event with ID '{eventId}' because no Subscription ID was included in the Event.", stripeEvent.Id);
return eventSubscription;
}
var subscription = await _stripeFacade.GetSubscription(eventSubscription.Id, new SubscriptionGetOptions { Expand = expand });
if (subscription == null)
@ -132,7 +165,7 @@ public class StripeEventService : IStripeEventService
(await GetCharge(stripeEvent, true, customerExpansion))?.Customer?.Metadata,
HandledStripeWebhook.UpcomingInvoice =>
(await GetInvoice(stripeEvent, true, customerExpansion))?.Customer?.Metadata,
await GetCustomerMetadataFromUpcomingInvoiceEvent(stripeEvent),
HandledStripeWebhook.PaymentSucceeded or HandledStripeWebhook.PaymentFailed or HandledStripeWebhook.InvoiceCreated =>
(await GetInvoice(stripeEvent, true, customerExpansion))?.Customer?.Metadata,
@ -154,6 +187,20 @@ public class StripeEventService : IStripeEventService
var customerRegion = GetCustomerRegion(customerMetadata);
return customerRegion == serverRegion;
/* In Stripe, when we receive an invoice.upcoming event, the event does not include an Invoice ID because
the invoice hasn't been created yet. Therefore, rather than retrieving the fresh Invoice with a 'customer'
expansion, we need to use the Customer ID on the event to retrieve the metadata. */
async Task<Dictionary<string, string>> GetCustomerMetadataFromUpcomingInvoiceEvent(Event localStripeEvent)
{
var invoice = await GetInvoice(localStripeEvent);
var customer = !string.IsNullOrEmpty(invoice.CustomerId)
? await _stripeFacade.GetCustomer(invoice.CustomerId)
: null;
return customer?.Metadata;
}
}
private static T Extract<T>(Event stripeEvent)