1
0
mirror of https://github.com/bitwarden/server.git synced 2025-07-13 13:47:30 -05:00
Files
bitwarden/src/Core/Billing/Payment/Commands/UpdateBillingAddressCommand.cs
Alex Morask 7f65a655d4 [PM-21881] Manage payment details outside of checkout (#6032)
* Add feature flag

* Further establish billing command pattern and use in PreviewTaxAmountCommand

* Add billing address models/commands/queries/tests

* Update TypeReadingJsonConverter to account for new union types

* Add payment method models/commands/queries/tests

* Add credit models/commands/queries/tests

* Add command/query registrations

* Add new endpoints to support new command model and payment functionality

* Run dotnet format

* Add InjectUserAttribute for easier AccountBillilngVNextController handling

* Add InjectOrganizationAttribute for easier OrganizationBillingVNextController handling

* Add InjectProviderAttribute for easier ProviderBillingVNextController handling

* Add XML documentation for billing command pipeline

* Fix StripeConstants post-nullability

* More nullability cleanup

* Run dotnet format
2025-07-10 08:32:25 -05:00

130 lines
4.8 KiB
C#

#nullable enable
using Bit.Core.Billing.Commands;
using Bit.Core.Billing.Constants;
using Bit.Core.Billing.Extensions;
using Bit.Core.Billing.Payment.Models;
using Bit.Core.Entities;
using Bit.Core.Services;
using Microsoft.Extensions.Logging;
using Stripe;
namespace Bit.Core.Billing.Payment.Commands;
public interface IUpdateBillingAddressCommand
{
Task<BillingCommandResult<BillingAddress>> Run(
ISubscriber subscriber,
BillingAddress billingAddress);
}
public class UpdateBillingAddressCommand(
ILogger<UpdateBillingAddressCommand> logger,
IStripeAdapter stripeAdapter) : BillingCommand<UpdateBillingAddressCommand>(logger), IUpdateBillingAddressCommand
{
public Task<BillingCommandResult<BillingAddress>> Run(
ISubscriber subscriber,
BillingAddress billingAddress) => HandleAsync(() => subscriber.GetProductUsageType() switch
{
ProductUsageType.Personal => UpdatePersonalBillingAddressAsync(subscriber, billingAddress),
ProductUsageType.Business => UpdateBusinessBillingAddressAsync(subscriber, billingAddress)
});
private async Task<BillingCommandResult<BillingAddress>> UpdatePersonalBillingAddressAsync(
ISubscriber subscriber,
BillingAddress billingAddress)
{
var customer =
await stripeAdapter.CustomerUpdateAsync(subscriber.GatewayCustomerId,
new CustomerUpdateOptions
{
Address = new AddressOptions
{
Country = billingAddress.Country,
PostalCode = billingAddress.PostalCode,
Line1 = billingAddress.Line1,
Line2 = billingAddress.Line2,
City = billingAddress.City,
State = billingAddress.State
},
Expand = ["subscriptions"]
});
await EnableAutomaticTaxAsync(subscriber, customer);
return BillingAddress.From(customer.Address);
}
private async Task<BillingCommandResult<BillingAddress>> UpdateBusinessBillingAddressAsync(
ISubscriber subscriber,
BillingAddress billingAddress)
{
var customer =
await stripeAdapter.CustomerUpdateAsync(subscriber.GatewayCustomerId,
new CustomerUpdateOptions
{
Address = new AddressOptions
{
Country = billingAddress.Country,
PostalCode = billingAddress.PostalCode,
Line1 = billingAddress.Line1,
Line2 = billingAddress.Line2,
City = billingAddress.City,
State = billingAddress.State
},
Expand = ["subscriptions", "tax_ids"],
TaxExempt = billingAddress.Country != "US"
? StripeConstants.TaxExempt.Reverse
: StripeConstants.TaxExempt.None
});
await EnableAutomaticTaxAsync(subscriber, customer);
var deleteExistingTaxIds = customer.TaxIds?.Any() ?? false
? customer.TaxIds.Select(taxId => stripeAdapter.TaxIdDeleteAsync(customer.Id, taxId.Id)).ToList()
: [];
if (billingAddress.TaxId == null)
{
await Task.WhenAll(deleteExistingTaxIds);
return BillingAddress.From(customer.Address);
}
var updatedTaxId = await stripeAdapter.TaxIdCreateAsync(customer.Id,
new TaxIdCreateOptions { Type = billingAddress.TaxId.Code, Value = billingAddress.TaxId.Value });
if (billingAddress.TaxId.Code == StripeConstants.TaxIdType.SpanishNIF)
{
updatedTaxId = await stripeAdapter.TaxIdCreateAsync(customer.Id,
new TaxIdCreateOptions
{
Type = StripeConstants.TaxIdType.EUVAT,
Value = $"ES{billingAddress.TaxId.Value}"
});
}
await Task.WhenAll(deleteExistingTaxIds);
return BillingAddress.From(customer.Address, updatedTaxId);
}
private async Task EnableAutomaticTaxAsync(
ISubscriber subscriber,
Customer customer)
{
if (!string.IsNullOrEmpty(subscriber.GatewaySubscriptionId))
{
var subscription = customer.Subscriptions.FirstOrDefault(subscription =>
subscription.Id == subscriber.GatewaySubscriptionId);
if (subscription is { AutomaticTax.Enabled: false })
{
await stripeAdapter.SubscriptionUpdateAsync(subscriber.GatewaySubscriptionId,
new SubscriptionUpdateOptions
{
AutomaticTax = new SubscriptionAutomaticTaxOptions { Enabled = true }
});
}
}
}
}