mirror of
https://github.com/bitwarden/server
synced 2025-12-21 10:43:44 +00:00
* move billing services+tests to billing namespaces * reorganized methods in file and added comment headers * renamed StripeAdapter methods for better clarity * clean up redundant qualifiers * Upgrade Stripe.net to v48.4.0 * Update PreviewTaxAmountCommand * Remove unused UpcomingInvoiceOptionExtensions * Added SubscriptionExtensions with GetCurrentPeriodEnd * Update PremiumUserBillingService * Update OrganizationBillingService * Update GetOrganizationWarningsQuery * Update BillingHistoryInfo * Update SubscriptionInfo * Remove unused Sql Billing folder * Update StripeAdapter * Update StripePaymentService * Update InvoiceCreatedHandler * Update PaymentFailedHandler * Update PaymentSucceededHandler * Update ProviderEventService * Update StripeEventUtilityService * Update SubscriptionDeletedHandler * Update SubscriptionUpdatedHandler * Update UpcomingInvoiceHandler * Update ProviderSubscriptionResponse * Remove unused Stripe Subscriptions Admin Tool * Update RemoveOrganizationFromProviderCommand * Update ProviderBillingService * Update RemoveOrganizatinoFromProviderCommandTests * Update PreviewTaxAmountCommandTests * Update GetCloudOrganizationLicenseQueryTests * Update GetOrganizationWarningsQueryTests * Update StripePaymentServiceTests * Update ProviderBillingControllerTests * Update ProviderEventServiceTests * Update SubscriptionDeletedHandlerTests * Update SubscriptionUpdatedHandlerTests * Resolve Billing test failures I completely removed tests for the StripeEventService as they were using a system I setup a while back that read JSON files of the Stripe event structure. I did not anticipate how frequently these structures would change with each API version and the cost of trying to update these specific JSON files to test a very static data retrieval service far outweigh the benefit. * Resolve Core test failures * Run dotnet format * Remove unused provider migration * Fixed failing tests * Run dotnet format * Replace the old webhook secret key with new one (#6223) * Fix compilation failures in additions * Run dotnet format * Bump Stripe API version * Fix recent addition: CreatePremiumCloudHostedSubscriptionCommand * Fix new code in main according to Stripe update * Fix InvoiceExtensions * Bump SDK version to match API Version * cleanup * fixing items missed after the merge * use expression body for all simple returns * forgot fixes, format, and pr feedback * claude pr feedback * pr feedback and cleanup * more claude feedback --------- Co-authored-by: Alex Morask <amorask@bitwarden.com> Co-authored-by: cyprain-okeke <108260115+cyprain-okeke@users.noreply.github.com>
411 lines
13 KiB
C#
411 lines
13 KiB
C#
using Bit.Core.AdminConsole.Entities;
|
|
using Bit.Core.Billing.Constants;
|
|
using Bit.Core.Billing.Enums;
|
|
using Bit.Core.Billing.Payment.Commands;
|
|
using Bit.Core.Billing.Payment.Models;
|
|
using Bit.Core.Billing.Services;
|
|
using Bit.Core.Test.Billing.Extensions;
|
|
using Microsoft.Extensions.Logging;
|
|
using NSubstitute;
|
|
using Stripe;
|
|
using Xunit;
|
|
|
|
namespace Bit.Core.Test.Billing.Payment.Commands;
|
|
|
|
using static StripeConstants;
|
|
|
|
public class UpdateBillingAddressCommandTests
|
|
{
|
|
private readonly ISubscriberService _subscriberService = Substitute.For<ISubscriberService>();
|
|
private readonly IStripeAdapter _stripeAdapter = Substitute.For<IStripeAdapter>();
|
|
private readonly UpdateBillingAddressCommand _command;
|
|
|
|
public UpdateBillingAddressCommandTests()
|
|
{
|
|
_command = new UpdateBillingAddressCommand(
|
|
Substitute.For<ILogger<UpdateBillingAddressCommand>>(),
|
|
_subscriberService,
|
|
_stripeAdapter);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Run_PersonalOrganization_MakesCorrectInvocations_ReturnsBillingAddress()
|
|
{
|
|
var organization = new Organization
|
|
{
|
|
PlanType = PlanType.FamiliesAnnually,
|
|
GatewayCustomerId = "cus_123",
|
|
GatewaySubscriptionId = "sub_123"
|
|
};
|
|
|
|
var input = new BillingAddress
|
|
{
|
|
Country = "US",
|
|
PostalCode = "12345",
|
|
Line1 = "123 Main St.",
|
|
Line2 = "Suite 100",
|
|
City = "New York",
|
|
State = "NY"
|
|
};
|
|
|
|
var customer = new Customer
|
|
{
|
|
Address = new Address
|
|
{
|
|
Country = "US",
|
|
PostalCode = "12345",
|
|
Line1 = "123 Main St.",
|
|
Line2 = "Suite 100",
|
|
City = "New York",
|
|
State = "NY"
|
|
},
|
|
Subscriptions = new StripeList<Subscription>
|
|
{
|
|
Data =
|
|
[
|
|
new Subscription
|
|
{
|
|
Id = organization.GatewaySubscriptionId,
|
|
AutomaticTax = new SubscriptionAutomaticTax { Enabled = false }
|
|
}
|
|
]
|
|
}
|
|
};
|
|
|
|
_stripeAdapter.UpdateCustomerAsync(organization.GatewayCustomerId, Arg.Is<CustomerUpdateOptions>(options =>
|
|
options.Address.Matches(input) &&
|
|
options.HasExpansions("subscriptions")
|
|
)).Returns(customer);
|
|
|
|
var result = await _command.Run(organization, input);
|
|
|
|
Assert.True(result.IsT0);
|
|
var output = result.AsT0;
|
|
Assert.Equivalent(input, output);
|
|
|
|
await _stripeAdapter.Received(1).UpdateSubscriptionAsync(organization.GatewaySubscriptionId,
|
|
Arg.Is<SubscriptionUpdateOptions>(options => options.AutomaticTax.Enabled == true));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Run_PersonalOrganization_NoCurrentCustomer_MakesCorrectInvocations_ReturnsBillingAddress()
|
|
{
|
|
var organization = new Organization
|
|
{
|
|
PlanType = PlanType.FamiliesAnnually,
|
|
GatewaySubscriptionId = "sub_123"
|
|
};
|
|
|
|
var input = new BillingAddress
|
|
{
|
|
Country = "US",
|
|
PostalCode = "12345",
|
|
Line1 = "123 Main St.",
|
|
Line2 = "Suite 100",
|
|
City = "New York",
|
|
State = "NY"
|
|
};
|
|
|
|
var customer = new Customer
|
|
{
|
|
Address = new Address
|
|
{
|
|
Country = "US",
|
|
PostalCode = "12345",
|
|
Line1 = "123 Main St.",
|
|
Line2 = "Suite 100",
|
|
City = "New York",
|
|
State = "NY"
|
|
},
|
|
Subscriptions = new StripeList<Subscription>
|
|
{
|
|
Data =
|
|
[
|
|
new Subscription
|
|
{
|
|
Id = organization.GatewaySubscriptionId,
|
|
AutomaticTax = new SubscriptionAutomaticTax { Enabled = false }
|
|
}
|
|
]
|
|
}
|
|
};
|
|
|
|
_stripeAdapter.UpdateCustomerAsync(organization.GatewayCustomerId, Arg.Is<CustomerUpdateOptions>(options =>
|
|
options.Address.Matches(input) &&
|
|
options.HasExpansions("subscriptions")
|
|
)).Returns(customer);
|
|
|
|
var result = await _command.Run(organization, input);
|
|
|
|
Assert.True(result.IsT0);
|
|
var output = result.AsT0;
|
|
Assert.Equivalent(input, output);
|
|
|
|
await _subscriberService.Received(1).CreateStripeCustomer(organization);
|
|
|
|
await _stripeAdapter.Received(1).UpdateSubscriptionAsync(organization.GatewaySubscriptionId,
|
|
Arg.Is<SubscriptionUpdateOptions>(options => options.AutomaticTax.Enabled == true));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Run_BusinessOrganization_MakesCorrectInvocations_ReturnsBillingAddress()
|
|
{
|
|
var organization = new Organization
|
|
{
|
|
PlanType = PlanType.EnterpriseAnnually,
|
|
GatewayCustomerId = "cus_123",
|
|
GatewaySubscriptionId = "sub_123"
|
|
};
|
|
|
|
var input = new BillingAddress
|
|
{
|
|
Country = "US",
|
|
PostalCode = "12345",
|
|
Line1 = "123 Main St.",
|
|
Line2 = "Suite 100",
|
|
City = "New York",
|
|
State = "NY"
|
|
};
|
|
|
|
var customer = new Customer
|
|
{
|
|
Address = new Address
|
|
{
|
|
Country = "US",
|
|
PostalCode = "12345",
|
|
Line1 = "123 Main St.",
|
|
Line2 = "Suite 100",
|
|
City = "New York",
|
|
State = "NY"
|
|
},
|
|
Subscriptions = new StripeList<Subscription>
|
|
{
|
|
Data =
|
|
[
|
|
new Subscription
|
|
{
|
|
Id = organization.GatewaySubscriptionId,
|
|
AutomaticTax = new SubscriptionAutomaticTax { Enabled = false }
|
|
}
|
|
]
|
|
}
|
|
};
|
|
|
|
_stripeAdapter.UpdateCustomerAsync(organization.GatewayCustomerId, Arg.Is<CustomerUpdateOptions>(options =>
|
|
options.Address.Matches(input) &&
|
|
options.HasExpansions("subscriptions", "tax_ids") &&
|
|
options.TaxExempt == TaxExempt.None
|
|
)).Returns(customer);
|
|
|
|
var result = await _command.Run(organization, input);
|
|
|
|
Assert.True(result.IsT0);
|
|
var output = result.AsT0;
|
|
Assert.Equivalent(input, output);
|
|
|
|
await _stripeAdapter.Received(1).UpdateSubscriptionAsync(organization.GatewaySubscriptionId,
|
|
Arg.Is<SubscriptionUpdateOptions>(options => options.AutomaticTax.Enabled == true));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Run_BusinessOrganization_RemovingTaxId_MakesCorrectInvocations_ReturnsBillingAddress()
|
|
{
|
|
var organization = new Organization
|
|
{
|
|
PlanType = PlanType.EnterpriseAnnually,
|
|
GatewayCustomerId = "cus_123",
|
|
GatewaySubscriptionId = "sub_123"
|
|
};
|
|
|
|
var input = new BillingAddress
|
|
{
|
|
Country = "US",
|
|
PostalCode = "12345",
|
|
Line1 = "123 Main St.",
|
|
Line2 = "Suite 100",
|
|
City = "New York",
|
|
State = "NY"
|
|
};
|
|
|
|
var customer = new Customer
|
|
{
|
|
Address = new Address
|
|
{
|
|
Country = "US",
|
|
PostalCode = "12345",
|
|
Line1 = "123 Main St.",
|
|
Line2 = "Suite 100",
|
|
City = "New York",
|
|
State = "NY"
|
|
},
|
|
Id = organization.GatewayCustomerId,
|
|
Subscriptions = new StripeList<Subscription>
|
|
{
|
|
Data =
|
|
[
|
|
new Subscription
|
|
{
|
|
Id = organization.GatewaySubscriptionId,
|
|
AutomaticTax = new SubscriptionAutomaticTax { Enabled = false }
|
|
}
|
|
]
|
|
},
|
|
TaxIds = new StripeList<TaxId>
|
|
{
|
|
Data =
|
|
[
|
|
new TaxId { Id = "tax_id_123", Type = "us_ein", Value = "123456789" }
|
|
]
|
|
}
|
|
};
|
|
|
|
_stripeAdapter.UpdateCustomerAsync(organization.GatewayCustomerId, Arg.Is<CustomerUpdateOptions>(options =>
|
|
options.Address.Matches(input) &&
|
|
options.HasExpansions("subscriptions", "tax_ids") &&
|
|
options.TaxExempt == TaxExempt.None
|
|
)).Returns(customer);
|
|
|
|
var result = await _command.Run(organization, input);
|
|
|
|
Assert.True(result.IsT0);
|
|
var output = result.AsT0;
|
|
Assert.Equivalent(input, output);
|
|
|
|
await _stripeAdapter.Received(1).UpdateSubscriptionAsync(organization.GatewaySubscriptionId,
|
|
Arg.Is<SubscriptionUpdateOptions>(options => options.AutomaticTax.Enabled == true));
|
|
|
|
await _stripeAdapter.Received(1).DeleteTaxIdAsync(customer.Id, "tax_id_123");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Run_NonUSBusinessOrganization_MakesCorrectInvocations_ReturnsBillingAddress()
|
|
{
|
|
var organization = new Organization
|
|
{
|
|
PlanType = PlanType.EnterpriseAnnually,
|
|
GatewayCustomerId = "cus_123",
|
|
GatewaySubscriptionId = "sub_123"
|
|
};
|
|
|
|
var input = new BillingAddress
|
|
{
|
|
Country = "DE",
|
|
PostalCode = "10115",
|
|
Line1 = "Friedrichstraße 123",
|
|
Line2 = "Stock 3",
|
|
City = "Berlin",
|
|
State = "Berlin"
|
|
};
|
|
|
|
var customer = new Customer
|
|
{
|
|
Address = new Address
|
|
{
|
|
Country = "DE",
|
|
PostalCode = "10115",
|
|
Line1 = "Friedrichstraße 123",
|
|
Line2 = "Stock 3",
|
|
City = "Berlin",
|
|
State = "Berlin"
|
|
},
|
|
Subscriptions = new StripeList<Subscription>
|
|
{
|
|
Data =
|
|
[
|
|
new Subscription
|
|
{
|
|
Id = organization.GatewaySubscriptionId,
|
|
AutomaticTax = new SubscriptionAutomaticTax { Enabled = false }
|
|
}
|
|
]
|
|
}
|
|
};
|
|
|
|
_stripeAdapter.UpdateCustomerAsync(organization.GatewayCustomerId, Arg.Is<CustomerUpdateOptions>(options =>
|
|
options.Address.Matches(input) &&
|
|
options.HasExpansions("subscriptions", "tax_ids") &&
|
|
options.TaxExempt == TaxExempt.Reverse
|
|
)).Returns(customer);
|
|
|
|
var result = await _command.Run(organization, input);
|
|
|
|
Assert.True(result.IsT0);
|
|
var output = result.AsT0;
|
|
Assert.Equivalent(input, output);
|
|
|
|
await _stripeAdapter.Received(1).UpdateSubscriptionAsync(organization.GatewaySubscriptionId,
|
|
Arg.Is<SubscriptionUpdateOptions>(options => options.AutomaticTax.Enabled == true));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Run_BusinessOrganizationWithSpanishCIF_MakesCorrectInvocations_ReturnsBillingAddress()
|
|
{
|
|
var organization = new Organization
|
|
{
|
|
PlanType = PlanType.EnterpriseAnnually,
|
|
GatewayCustomerId = "cus_123",
|
|
GatewaySubscriptionId = "sub_123"
|
|
};
|
|
|
|
var input = new BillingAddress
|
|
{
|
|
Country = "ES",
|
|
PostalCode = "28001",
|
|
Line1 = "Calle de Serrano 41",
|
|
Line2 = "Planta 3",
|
|
City = "Madrid",
|
|
State = "Madrid",
|
|
TaxId = new TaxID(TaxIdType.SpanishNIF, "A12345678")
|
|
};
|
|
|
|
var customer = new Customer
|
|
{
|
|
Address = new Address
|
|
{
|
|
Country = "ES",
|
|
PostalCode = "28001",
|
|
Line1 = "Calle de Serrano 41",
|
|
Line2 = "Planta 3",
|
|
City = "Madrid",
|
|
State = "Madrid"
|
|
},
|
|
Id = organization.GatewayCustomerId,
|
|
Subscriptions = new StripeList<Subscription>
|
|
{
|
|
Data =
|
|
[
|
|
new Subscription
|
|
{
|
|
Id = organization.GatewaySubscriptionId,
|
|
AutomaticTax = new SubscriptionAutomaticTax { Enabled = false }
|
|
}
|
|
]
|
|
}
|
|
};
|
|
|
|
_stripeAdapter.UpdateCustomerAsync(organization.GatewayCustomerId, Arg.Is<CustomerUpdateOptions>(options =>
|
|
options.Address.Matches(input) &&
|
|
options.HasExpansions("subscriptions", "tax_ids") &&
|
|
options.TaxExempt == TaxExempt.Reverse
|
|
)).Returns(customer);
|
|
|
|
_stripeAdapter
|
|
.CreateTaxIdAsync(customer.Id,
|
|
Arg.Is<TaxIdCreateOptions>(options => options.Type == TaxIdType.EUVAT))
|
|
.Returns(new TaxId { Type = TaxIdType.EUVAT, Value = "ESA12345678" });
|
|
|
|
var result = await _command.Run(organization, input);
|
|
|
|
Assert.True(result.IsT0);
|
|
var output = result.AsT0;
|
|
Assert.Equivalent(input with { TaxId = new TaxID(TaxIdType.EUVAT, "ESA12345678") }, output);
|
|
|
|
await _stripeAdapter.Received(1).UpdateSubscriptionAsync(organization.GatewaySubscriptionId,
|
|
Arg.Is<SubscriptionUpdateOptions>(options => options.AutomaticTax.Enabled == true));
|
|
|
|
await _stripeAdapter.Received(1).CreateTaxIdAsync(organization.GatewayCustomerId, Arg.Is<TaxIdCreateOptions>(
|
|
options => options.Type == TaxIdType.SpanishNIF &&
|
|
options.Value == input.TaxId.Value));
|
|
}
|
|
}
|