mirror of
https://github.com/bitwarden/server.git
synced 2025-07-04 01:22:50 -05:00
[PM-19566] Update MSPs to "charge_automatically" with Admin-based opt-out (#5650)
* Update provider to charge automatically with Admin Portal-based opt-out * Design feedback * Run dotnet format
This commit is contained in:
@ -16,6 +16,12 @@ public interface IStripeFacade
|
||||
RequestOptions requestOptions = null,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
Task<Customer> UpdateCustomer(
|
||||
string customerId,
|
||||
CustomerUpdateOptions customerUpdateOptions = null,
|
||||
RequestOptions requestOptions = null,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
Task<Event> GetEvent(
|
||||
string eventId,
|
||||
EventGetOptions eventGetOptions = null,
|
||||
|
@ -1,4 +1,8 @@
|
||||
using Bit.Billing.Constants;
|
||||
using Bit.Core;
|
||||
using Bit.Core.Billing.Constants;
|
||||
using Bit.Core.Billing.Extensions;
|
||||
using Bit.Core.Services;
|
||||
using Stripe;
|
||||
using Event = Stripe.Event;
|
||||
|
||||
@ -10,20 +14,114 @@ public class PaymentMethodAttachedHandler : IPaymentMethodAttachedHandler
|
||||
private readonly IStripeEventService _stripeEventService;
|
||||
private readonly IStripeFacade _stripeFacade;
|
||||
private readonly IStripeEventUtilityService _stripeEventUtilityService;
|
||||
private readonly IFeatureService _featureService;
|
||||
|
||||
public PaymentMethodAttachedHandler(
|
||||
ILogger<PaymentMethodAttachedHandler> logger,
|
||||
IStripeEventService stripeEventService,
|
||||
IStripeFacade stripeFacade,
|
||||
IStripeEventUtilityService stripeEventUtilityService)
|
||||
IStripeEventUtilityService stripeEventUtilityService,
|
||||
IFeatureService featureService)
|
||||
{
|
||||
_logger = logger;
|
||||
_stripeEventService = stripeEventService;
|
||||
_stripeFacade = stripeFacade;
|
||||
_stripeEventUtilityService = stripeEventUtilityService;
|
||||
_featureService = featureService;
|
||||
}
|
||||
|
||||
public async Task HandleAsync(Event parsedEvent)
|
||||
{
|
||||
var updateMSPToChargeAutomatically =
|
||||
_featureService.IsEnabled(FeatureFlagKeys.PM199566_UpdateMSPToChargeAutomatically);
|
||||
|
||||
if (updateMSPToChargeAutomatically)
|
||||
{
|
||||
await HandleVNextAsync(parsedEvent);
|
||||
}
|
||||
else
|
||||
{
|
||||
await HandleVCurrentAsync(parsedEvent);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandleVNextAsync(Event parsedEvent)
|
||||
{
|
||||
var paymentMethod = await _stripeEventService.GetPaymentMethod(parsedEvent, true, ["customer.subscriptions.data.latest_invoice"]);
|
||||
|
||||
if (paymentMethod == null)
|
||||
{
|
||||
_logger.LogWarning("Attempted to handle the event payment_method.attached but paymentMethod was null");
|
||||
return;
|
||||
}
|
||||
|
||||
var customer = paymentMethod.Customer;
|
||||
var subscriptions = customer?.Subscriptions;
|
||||
|
||||
// This represents a provider subscription set to "send_invoice" that was paid using a Stripe hosted invoice payment page.
|
||||
var invoicedProviderSubscription = subscriptions?.Data.FirstOrDefault(subscription =>
|
||||
subscription.Metadata.ContainsKey(StripeConstants.MetadataKeys.ProviderId) &&
|
||||
subscription.Status != StripeConstants.SubscriptionStatus.Canceled &&
|
||||
subscription.CollectionMethod == StripeConstants.CollectionMethod.SendInvoice);
|
||||
|
||||
/*
|
||||
* If we have an invoiced provider subscription where the customer hasn't been marked as invoice-approved,
|
||||
* we need to try and set the default payment method and update the collection method to be "charge_automatically".
|
||||
*/
|
||||
if (invoicedProviderSubscription != null && !customer.ApprovedToPayByInvoice())
|
||||
{
|
||||
if (customer.InvoiceSettings.DefaultPaymentMethodId != paymentMethod.Id)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _stripeFacade.UpdateCustomer(customer.Id,
|
||||
new CustomerUpdateOptions
|
||||
{
|
||||
InvoiceSettings = new CustomerInvoiceSettingsOptions
|
||||
{
|
||||
DefaultPaymentMethod = paymentMethod.Id
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
_logger.LogWarning(exception,
|
||||
"Failed to set customer's ({CustomerID}) default payment method during 'payment_method.attached' webhook",
|
||||
customer.Id);
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await _stripeFacade.UpdateSubscription(invoicedProviderSubscription.Id,
|
||||
new SubscriptionUpdateOptions
|
||||
{
|
||||
CollectionMethod = StripeConstants.CollectionMethod.ChargeAutomatically
|
||||
});
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
_logger.LogWarning(exception,
|
||||
"Failed to set subscription's ({SubscriptionID}) collection method to 'charge_automatically' during 'payment_method.attached' webhook",
|
||||
customer.Id);
|
||||
}
|
||||
}
|
||||
|
||||
var unpaidSubscriptions = subscriptions?.Data.Where(subscription =>
|
||||
subscription.Status == StripeConstants.SubscriptionStatus.Unpaid).ToList();
|
||||
|
||||
if (unpaidSubscriptions == null || unpaidSubscriptions.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var unpaidSubscription in unpaidSubscriptions)
|
||||
{
|
||||
await AttemptToPayOpenSubscriptionAsync(unpaidSubscription);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandleVCurrentAsync(Event parsedEvent)
|
||||
{
|
||||
var paymentMethod = await _stripeEventService.GetPaymentMethod(parsedEvent);
|
||||
if (paymentMethod is null)
|
||||
|
@ -33,6 +33,13 @@ public class StripeFacade : IStripeFacade
|
||||
CancellationToken cancellationToken = default) =>
|
||||
await _customerService.GetAsync(customerId, customerGetOptions, requestOptions, cancellationToken);
|
||||
|
||||
public async Task<Customer> UpdateCustomer(
|
||||
string customerId,
|
||||
CustomerUpdateOptions customerUpdateOptions = null,
|
||||
RequestOptions requestOptions = null,
|
||||
CancellationToken cancellationToken = default) =>
|
||||
await _customerService.UpdateAsync(customerId, customerUpdateOptions, requestOptions, cancellationToken);
|
||||
|
||||
public async Task<Invoice> GetInvoice(
|
||||
string invoiceId,
|
||||
InvoiceGetOptions invoiceGetOptions = null,
|
||||
|
Reference in New Issue
Block a user