mirror of
https://github.com/bitwarden/server
synced 2025-12-22 11:13:27 +00:00
[PM-24273] Milestone 2C (#6544)
* feat(billing): add mjml template and updated templates * feat(billing): update maileservices * feat(billing): add milestone2 discount * feat(billing): add milestone 2 updates and stripe constants * tests(billing): add handler tests * fix(billing): update mailer view and templates * fix(billing): revert mailservice changes * fix(billing): swap mailer service in handler * test(billing): update handler tests
This commit is contained in:
@@ -2,18 +2,22 @@
|
||||
|
||||
#nullable disable
|
||||
|
||||
using Bit.Core;
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.Entities.Provider;
|
||||
using Bit.Core.AdminConsole.Repositories;
|
||||
using Bit.Core.Billing.Constants;
|
||||
using Bit.Core.Billing.Enums;
|
||||
using Bit.Core.Billing.Extensions;
|
||||
using Bit.Core.Billing.Payment.Queries;
|
||||
using Bit.Core.Billing.Pricing;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Models.Mail.UpdatedInvoiceIncoming;
|
||||
using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Interfaces;
|
||||
using Bit.Core.Platform.Mail.Mailer;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Stripe;
|
||||
using static Bit.Core.Billing.Constants.StripeConstants;
|
||||
using Event = Stripe.Event;
|
||||
|
||||
namespace Bit.Billing.Services.Implementations;
|
||||
@@ -29,7 +33,9 @@ public class UpcomingInvoiceHandler(
|
||||
IStripeEventService stripeEventService,
|
||||
IStripeEventUtilityService stripeEventUtilityService,
|
||||
IUserRepository userRepository,
|
||||
IValidateSponsorshipCommand validateSponsorshipCommand)
|
||||
IValidateSponsorshipCommand validateSponsorshipCommand,
|
||||
IMailer mailer,
|
||||
IFeatureService featureService)
|
||||
: IUpcomingInvoiceHandler
|
||||
{
|
||||
public async Task HandleAsync(Event parsedEvent)
|
||||
@@ -37,7 +43,8 @@ public class UpcomingInvoiceHandler(
|
||||
var invoice = await stripeEventService.GetInvoice(parsedEvent);
|
||||
|
||||
var customer =
|
||||
await stripeFacade.GetCustomer(invoice.CustomerId, new CustomerGetOptions { Expand = ["subscriptions", "tax", "tax_ids"] });
|
||||
await stripeFacade.GetCustomer(invoice.CustomerId,
|
||||
new CustomerGetOptions { Expand = ["subscriptions", "tax", "tax_ids"] });
|
||||
|
||||
var subscription = customer.Subscriptions.FirstOrDefault();
|
||||
|
||||
@@ -68,7 +75,8 @@ public class UpcomingInvoiceHandler(
|
||||
|
||||
if (stripeEventUtilityService.IsSponsoredSubscription(subscription))
|
||||
{
|
||||
var sponsorshipIsValid = await validateSponsorshipCommand.ValidateSponsorshipAsync(organizationId.Value);
|
||||
var sponsorshipIsValid =
|
||||
await validateSponsorshipCommand.ValidateSponsorshipAsync(organizationId.Value);
|
||||
|
||||
if (!sponsorshipIsValid)
|
||||
{
|
||||
@@ -122,9 +130,17 @@ public class UpcomingInvoiceHandler(
|
||||
}
|
||||
}
|
||||
|
||||
var milestone2Feature = featureService.IsEnabled(FeatureFlagKeys.PM23341_Milestone_2);
|
||||
if (milestone2Feature)
|
||||
{
|
||||
await UpdateSubscriptionItemPriceIdAsync(parsedEvent, subscription, user);
|
||||
}
|
||||
|
||||
if (user.Premium)
|
||||
{
|
||||
await SendUpcomingInvoiceEmailsAsync(new List<string> { user.Email }, invoice);
|
||||
await (milestone2Feature
|
||||
? SendUpdatedUpcomingInvoiceEmailsAsync(new List<string> { user.Email })
|
||||
: SendUpcomingInvoiceEmailsAsync(new List<string> { user.Email }, invoice));
|
||||
}
|
||||
}
|
||||
else if (providerId.HasValue)
|
||||
@@ -142,6 +158,39 @@ public class UpcomingInvoiceHandler(
|
||||
}
|
||||
}
|
||||
|
||||
private async Task UpdateSubscriptionItemPriceIdAsync(Event parsedEvent, Subscription subscription, User user)
|
||||
{
|
||||
var pricingItem =
|
||||
subscription.Items.FirstOrDefault(i => i.Price.Id == Prices.PremiumAnnually);
|
||||
if (pricingItem != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var plan = await pricingClient.GetAvailablePremiumPlan();
|
||||
await stripeFacade.UpdateSubscription(subscription.Id,
|
||||
new SubscriptionUpdateOptions
|
||||
{
|
||||
Items =
|
||||
[
|
||||
new SubscriptionItemOptions { Id = pricingItem.Id, Price = plan.Seat.StripePriceId }
|
||||
],
|
||||
Discounts =
|
||||
[
|
||||
new SubscriptionDiscountOptions { Coupon = CouponIDs.Milestone2SubscriptionDiscount }
|
||||
]
|
||||
});
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
logger.LogError(
|
||||
exception,
|
||||
"Failed to update user's ({UserID}) subscription price id while processing event with ID {EventID}",
|
||||
user.Id,
|
||||
parsedEvent.Id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SendUpcomingInvoiceEmailsAsync(IEnumerable<string> emails, Invoice invoice)
|
||||
{
|
||||
var validEmails = emails.Where(e => !string.IsNullOrEmpty(e));
|
||||
@@ -159,7 +208,19 @@ public class UpcomingInvoiceHandler(
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SendProviderUpcomingInvoiceEmailsAsync(IEnumerable<string> emails, Invoice invoice, Subscription subscription, Guid providerId)
|
||||
private async Task SendUpdatedUpcomingInvoiceEmailsAsync(IEnumerable<string> emails)
|
||||
{
|
||||
var validEmails = emails.Where(e => !string.IsNullOrEmpty(e));
|
||||
var updatedUpcomingEmail = new UpdatedInvoiceUpcomingMail
|
||||
{
|
||||
ToEmails = validEmails,
|
||||
View = new UpdatedInvoiceUpcomingView()
|
||||
};
|
||||
await mailer.SendEmail(updatedUpcomingEmail);
|
||||
}
|
||||
|
||||
private async Task SendProviderUpcomingInvoiceEmailsAsync(IEnumerable<string> emails, Invoice invoice,
|
||||
Subscription subscription, Guid providerId)
|
||||
{
|
||||
var validEmails = emails.Where(e => !string.IsNullOrEmpty(e));
|
||||
|
||||
@@ -205,12 +266,12 @@ public class UpcomingInvoiceHandler(
|
||||
organization.PlanType.GetProductTier() != ProductTierType.Families &&
|
||||
customer.Address.Country != Core.Constants.CountryAbbreviations.UnitedStates;
|
||||
|
||||
if (nonUSBusinessUse && customer.TaxExempt != StripeConstants.TaxExempt.Reverse)
|
||||
if (nonUSBusinessUse && customer.TaxExempt != TaxExempt.Reverse)
|
||||
{
|
||||
try
|
||||
{
|
||||
await stripeFacade.UpdateCustomer(subscription.CustomerId,
|
||||
new CustomerUpdateOptions { TaxExempt = StripeConstants.TaxExempt.Reverse });
|
||||
new CustomerUpdateOptions { TaxExempt = TaxExempt.Reverse });
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
@@ -250,12 +311,12 @@ public class UpcomingInvoiceHandler(
|
||||
string eventId)
|
||||
{
|
||||
if (customer.Address.Country != Core.Constants.CountryAbbreviations.UnitedStates &&
|
||||
customer.TaxExempt != StripeConstants.TaxExempt.Reverse)
|
||||
customer.TaxExempt != TaxExempt.Reverse)
|
||||
{
|
||||
try
|
||||
{
|
||||
await stripeFacade.UpdateCustomer(subscription.CustomerId,
|
||||
new CustomerUpdateOptions { TaxExempt = StripeConstants.TaxExempt.Reverse });
|
||||
new CustomerUpdateOptions { TaxExempt = TaxExempt.Reverse });
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user