mirror of
https://github.com/bitwarden/server
synced 2025-12-24 04:03:25 +00:00
Sync Stripe customer details for Provider / Organization in API & Admin
This commit is contained in:
@@ -796,6 +796,44 @@ public class ProviderBillingService(
|
||||
}
|
||||
}
|
||||
|
||||
public async Task UpdateProviderNameAndEmail(Provider provider)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(provider.GatewayCustomerId))
|
||||
{
|
||||
logger.LogWarning(
|
||||
"Provider ({ProviderId}) has no Stripe customer to update",
|
||||
provider.Id);
|
||||
return;
|
||||
}
|
||||
|
||||
var newDisplayName = provider.DisplayName();
|
||||
|
||||
// Provider.DisplayName() can return null - handle gracefully
|
||||
if (string.IsNullOrWhiteSpace(newDisplayName))
|
||||
{
|
||||
logger.LogWarning(
|
||||
"Provider ({ProviderId}) has no name to update in Stripe",
|
||||
provider.Id);
|
||||
return;
|
||||
}
|
||||
|
||||
await stripeAdapter.CustomerUpdateAsync(provider.GatewayCustomerId,
|
||||
new CustomerUpdateOptions
|
||||
{
|
||||
Email = provider.BillingEmail,
|
||||
Description = newDisplayName,
|
||||
InvoiceSettings = new CustomerInvoiceSettingsOptions
|
||||
{
|
||||
CustomFields = [
|
||||
new CustomerInvoiceSettingsCustomFieldOptions
|
||||
{
|
||||
Name = provider.SubscriberType(),
|
||||
Value = newDisplayName
|
||||
}]
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private Func<int, Task> CurrySeatScalingUpdate(
|
||||
Provider provider,
|
||||
ProviderPlan providerPlan,
|
||||
|
||||
@@ -2151,4 +2151,151 @@ public class ProviderBillingServiceTests
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region UpdateProviderNameAndEmail
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task UpdateProviderNameAndEmail_NullGatewayCustomerId_LogsWarningAndReturns(
|
||||
Provider provider,
|
||||
SutProvider<ProviderBillingService> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
provider.GatewayCustomerId = null;
|
||||
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
|
||||
|
||||
// Act
|
||||
await sutProvider.Sut.UpdateProviderNameAndEmail(provider);
|
||||
|
||||
// Assert
|
||||
await stripeAdapter.DidNotReceive().CustomerUpdateAsync(
|
||||
Arg.Any<string>(),
|
||||
Arg.Any<CustomerUpdateOptions>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task UpdateProviderNameAndEmail_EmptyGatewayCustomerId_LogsWarningAndReturns(
|
||||
Provider provider,
|
||||
SutProvider<ProviderBillingService> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
provider.GatewayCustomerId = "";
|
||||
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
|
||||
|
||||
// Act
|
||||
await sutProvider.Sut.UpdateProviderNameAndEmail(provider);
|
||||
|
||||
// Assert
|
||||
await stripeAdapter.DidNotReceive().CustomerUpdateAsync(
|
||||
Arg.Any<string>(),
|
||||
Arg.Any<CustomerUpdateOptions>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task UpdateProviderNameAndEmail_NullProviderName_LogsWarningAndReturns(
|
||||
Provider provider,
|
||||
SutProvider<ProviderBillingService> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
provider.Name = null;
|
||||
provider.GatewayCustomerId = "cus_test123";
|
||||
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
|
||||
|
||||
// Act
|
||||
await sutProvider.Sut.UpdateProviderNameAndEmail(provider);
|
||||
|
||||
// Assert
|
||||
await stripeAdapter.DidNotReceive().CustomerUpdateAsync(
|
||||
Arg.Any<string>(),
|
||||
Arg.Any<CustomerUpdateOptions>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task UpdateProviderNameAndEmail_EmptyProviderName_LogsWarningAndReturns(
|
||||
Provider provider,
|
||||
SutProvider<ProviderBillingService> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
provider.Name = "";
|
||||
provider.GatewayCustomerId = "cus_test123";
|
||||
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
|
||||
|
||||
// Act
|
||||
await sutProvider.Sut.UpdateProviderNameAndEmail(provider);
|
||||
|
||||
// Assert
|
||||
await stripeAdapter.DidNotReceive().CustomerUpdateAsync(
|
||||
Arg.Any<string>(),
|
||||
Arg.Any<CustomerUpdateOptions>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task UpdateProviderNameAndEmail_ValidProvider_CallsStripeWithCorrectParameters(
|
||||
Provider provider,
|
||||
SutProvider<ProviderBillingService> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
provider.Name = "Test Provider";
|
||||
provider.BillingEmail = "billing@test.com";
|
||||
provider.GatewayCustomerId = "cus_test123";
|
||||
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
|
||||
|
||||
// Act
|
||||
await sutProvider.Sut.UpdateProviderNameAndEmail(provider);
|
||||
|
||||
// Assert
|
||||
await stripeAdapter.Received(1).CustomerUpdateAsync(
|
||||
provider.GatewayCustomerId,
|
||||
Arg.Is<CustomerUpdateOptions>(options =>
|
||||
options.Email == provider.BillingEmail &&
|
||||
options.Description == provider.Name &&
|
||||
options.InvoiceSettings.CustomFields.Count == 1 &&
|
||||
options.InvoiceSettings.CustomFields[0].Name == "Provider" &&
|
||||
options.InvoiceSettings.CustomFields[0].Value == provider.Name));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task UpdateProviderNameAndEmail_LongProviderName_UsesFullName(
|
||||
Provider provider,
|
||||
SutProvider<ProviderBillingService> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
var longName = new string('A', 50); // 50 characters
|
||||
provider.Name = longName;
|
||||
provider.BillingEmail = "billing@test.com";
|
||||
provider.GatewayCustomerId = "cus_test123";
|
||||
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
|
||||
|
||||
// Act
|
||||
await sutProvider.Sut.UpdateProviderNameAndEmail(provider);
|
||||
|
||||
// Assert
|
||||
await stripeAdapter.Received(1).CustomerUpdateAsync(
|
||||
provider.GatewayCustomerId,
|
||||
Arg.Is<CustomerUpdateOptions>(options =>
|
||||
options.InvoiceSettings.CustomFields[0].Value == longName));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task UpdateProviderNameAndEmail_NullBillingEmail_UpdatesWithNull(
|
||||
Provider provider,
|
||||
SutProvider<ProviderBillingService> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
provider.Name = "Test Provider";
|
||||
provider.BillingEmail = null;
|
||||
provider.GatewayCustomerId = "cus_test123";
|
||||
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
|
||||
|
||||
// Act
|
||||
await sutProvider.Sut.UpdateProviderNameAndEmail(provider);
|
||||
|
||||
// Assert
|
||||
await stripeAdapter.Received(1).CustomerUpdateAsync(
|
||||
provider.GatewayCustomerId,
|
||||
Arg.Is<CustomerUpdateOptions>(options =>
|
||||
options.Email == null &&
|
||||
options.Description == provider.Name));
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ using Bit.Core.AdminConsole.Providers.Interfaces;
|
||||
using Bit.Core.AdminConsole.Repositories;
|
||||
using Bit.Core.Billing.Enums;
|
||||
using Bit.Core.Billing.Extensions;
|
||||
using Bit.Core.Billing.Organizations.Services;
|
||||
using Bit.Core.Billing.Pricing;
|
||||
using Bit.Core.Billing.Providers.Services;
|
||||
using Bit.Core.Enums;
|
||||
@@ -56,6 +57,7 @@ public class OrganizationsController : Controller
|
||||
private readonly IOrganizationInitiateDeleteCommand _organizationInitiateDeleteCommand;
|
||||
private readonly IPricingClient _pricingClient;
|
||||
private readonly IResendOrganizationInviteCommand _resendOrganizationInviteCommand;
|
||||
private readonly IOrganizationBillingService _organizationBillingService;
|
||||
|
||||
public OrganizationsController(
|
||||
IOrganizationRepository organizationRepository,
|
||||
@@ -80,7 +82,8 @@ public class OrganizationsController : Controller
|
||||
IProviderBillingService providerBillingService,
|
||||
IOrganizationInitiateDeleteCommand organizationInitiateDeleteCommand,
|
||||
IPricingClient pricingClient,
|
||||
IResendOrganizationInviteCommand resendOrganizationInviteCommand)
|
||||
IResendOrganizationInviteCommand resendOrganizationInviteCommand,
|
||||
IOrganizationBillingService organizationBillingService)
|
||||
{
|
||||
_organizationRepository = organizationRepository;
|
||||
_organizationUserRepository = organizationUserRepository;
|
||||
@@ -105,6 +108,7 @@ public class OrganizationsController : Controller
|
||||
_organizationInitiateDeleteCommand = organizationInitiateDeleteCommand;
|
||||
_pricingClient = pricingClient;
|
||||
_resendOrganizationInviteCommand = resendOrganizationInviteCommand;
|
||||
_organizationBillingService = organizationBillingService;
|
||||
}
|
||||
|
||||
[RequirePermission(Permission.Org_List_View)]
|
||||
@@ -241,6 +245,8 @@ public class OrganizationsController : Controller
|
||||
var existingOrganizationData = new Organization
|
||||
{
|
||||
Id = organization.Id,
|
||||
Name = organization.Name,
|
||||
BillingEmail = organization.BillingEmail,
|
||||
Status = organization.Status,
|
||||
PlanType = organization.PlanType,
|
||||
Seats = organization.Seats
|
||||
@@ -286,6 +292,23 @@ public class OrganizationsController : Controller
|
||||
|
||||
await _applicationCacheService.UpsertOrganizationAbilityAsync(organization);
|
||||
|
||||
// Sync name/email changes to Stripe
|
||||
if (!string.IsNullOrEmpty(organization.GatewayCustomerId) &
|
||||
(existingOrganizationData.Name != organization.Name || existingOrganizationData.BillingEmail != organization.BillingEmail))
|
||||
{
|
||||
try
|
||||
{
|
||||
await _organizationBillingService.UpdateOrganizationNameAndEmail(organization);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex,
|
||||
"Failed to update Stripe customer for organization {OrganizationId}. Database was updated successfully.",
|
||||
organization.Id);
|
||||
TempData["Warning"] = "Organization updated successfully, but Stripe customer name/email synchronization failed.";
|
||||
}
|
||||
}
|
||||
|
||||
return RedirectToAction("Edit", new { id });
|
||||
}
|
||||
|
||||
|
||||
@@ -56,6 +56,7 @@ public class ProvidersController : Controller
|
||||
private readonly IStripeAdapter _stripeAdapter;
|
||||
private readonly IAccessControlService _accessControlService;
|
||||
private readonly ISubscriberService _subscriberService;
|
||||
private readonly ILogger<ProvidersController> _logger;
|
||||
|
||||
public ProvidersController(IOrganizationRepository organizationRepository,
|
||||
IResellerClientOrganizationSignUpCommand resellerClientOrganizationSignUpCommand,
|
||||
@@ -72,7 +73,8 @@ public class ProvidersController : Controller
|
||||
IPricingClient pricingClient,
|
||||
IStripeAdapter stripeAdapter,
|
||||
IAccessControlService accessControlService,
|
||||
ISubscriberService subscriberService)
|
||||
ISubscriberService subscriberService,
|
||||
ILogger<ProvidersController> logger)
|
||||
{
|
||||
_organizationRepository = organizationRepository;
|
||||
_resellerClientOrganizationSignUpCommand = resellerClientOrganizationSignUpCommand;
|
||||
@@ -92,6 +94,7 @@ public class ProvidersController : Controller
|
||||
_braintreeMerchantUrl = webHostEnvironment.GetBraintreeMerchantUrl();
|
||||
_braintreeMerchantId = globalSettings.Braintree.MerchantId;
|
||||
_subscriberService = subscriberService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
[RequirePermission(Permission.Provider_List_View)]
|
||||
@@ -296,6 +299,9 @@ public class ProvidersController : Controller
|
||||
|
||||
var originalProviderStatus = provider.Enabled;
|
||||
|
||||
// Capture original billing email before modifications for Stripe sync
|
||||
var originalBillingEmail = provider.BillingEmail;
|
||||
|
||||
model.ToProvider(provider);
|
||||
|
||||
// validate the stripe ids to prevent saving a bad one
|
||||
@@ -321,6 +327,22 @@ public class ProvidersController : Controller
|
||||
await _providerService.UpdateAsync(provider);
|
||||
await _applicationCacheService.UpsertProviderAbilityAsync(provider);
|
||||
|
||||
// Sync billing email changes to Stripe
|
||||
if (!string.IsNullOrEmpty(provider.GatewayCustomerId) && originalBillingEmail != provider.BillingEmail)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _providerBillingService.UpdateProviderNameAndEmail(provider);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex,
|
||||
"Failed to update Stripe customer for provider {ProviderId}. Database was updated successfully.",
|
||||
provider.Id);
|
||||
TempData["Warning"] = "Provider updated successfully, but Stripe customer email synchronization failed.";
|
||||
}
|
||||
}
|
||||
|
||||
if (!provider.IsBillable())
|
||||
{
|
||||
return RedirectToAction("Edit", new { id });
|
||||
|
||||
@@ -5,6 +5,7 @@ using Bit.Api.AdminConsole.Models.Request.Providers;
|
||||
using Bit.Api.AdminConsole.Models.Response.Providers;
|
||||
using Bit.Core.AdminConsole.Repositories;
|
||||
using Bit.Core.AdminConsole.Services;
|
||||
using Bit.Core.Billing.Providers.Services;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Services;
|
||||
@@ -23,15 +24,20 @@ public class ProvidersController : Controller
|
||||
private readonly IProviderService _providerService;
|
||||
private readonly ICurrentContext _currentContext;
|
||||
private readonly GlobalSettings _globalSettings;
|
||||
private readonly IProviderBillingService _providerBillingService;
|
||||
private readonly ILogger<ProvidersController> _logger;
|
||||
|
||||
public ProvidersController(IUserService userService, IProviderRepository providerRepository,
|
||||
IProviderService providerService, ICurrentContext currentContext, GlobalSettings globalSettings)
|
||||
IProviderService providerService, ICurrentContext currentContext, GlobalSettings globalSettings,
|
||||
IProviderBillingService providerBillingService, ILogger<ProvidersController> logger)
|
||||
{
|
||||
_userService = userService;
|
||||
_providerRepository = providerRepository;
|
||||
_providerService = providerService;
|
||||
_currentContext = currentContext;
|
||||
_globalSettings = globalSettings;
|
||||
_providerBillingService = providerBillingService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
[HttpGet("{id:guid}")]
|
||||
@@ -65,7 +71,29 @@ public class ProvidersController : Controller
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
// Capture original values before modifications for Stripe sync
|
||||
var originalName = provider.Name;
|
||||
var originalBillingEmail = provider.BillingEmail;
|
||||
var hasGatewayCustomerId = !string.IsNullOrWhiteSpace(provider.GatewayCustomerId);
|
||||
|
||||
await _providerService.UpdateAsync(model.ToProvider(provider, _globalSettings));
|
||||
|
||||
// Sync name/email changes to Stripe
|
||||
if (hasGatewayCustomerId &&
|
||||
(originalName != provider.Name || originalBillingEmail != provider.BillingEmail))
|
||||
{
|
||||
try
|
||||
{
|
||||
await _providerBillingService.UpdateProviderNameAndEmail(provider);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex,
|
||||
"Failed to update Stripe customer for provider {ProviderId}. Database was updated successfully.",
|
||||
provider.Id);
|
||||
}
|
||||
}
|
||||
|
||||
return new ProviderResponseModel(provider);
|
||||
}
|
||||
|
||||
|
||||
@@ -61,10 +61,6 @@ public interface IOrganizationBillingService
|
||||
/// Updates the organization name and email on the Stripe customer entry.
|
||||
/// This only updates Stripe, not the Bitwarden database.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The caller should ensure that the organization has a GatewayCustomerId before calling this method.
|
||||
/// </remarks>
|
||||
/// <param name="organization">The organization to update in Stripe.</param>
|
||||
/// <exception cref="BillingException">Thrown when the organization does not have a GatewayCustomerId.</exception>
|
||||
Task UpdateOrganizationNameAndEmail(Organization organization);
|
||||
}
|
||||
|
||||
@@ -178,13 +178,25 @@ public class OrganizationBillingService(
|
||||
|
||||
public async Task UpdateOrganizationNameAndEmail(Organization organization)
|
||||
{
|
||||
if (organization.GatewayCustomerId is null)
|
||||
if (string.IsNullOrWhiteSpace(organization.GatewayCustomerId))
|
||||
{
|
||||
throw new BillingException("Cannot update an organization in Stripe without a GatewayCustomerId.");
|
||||
logger.LogWarning(
|
||||
"Organization ({OrganizationId}) has no Stripe customer to update",
|
||||
organization.Id);
|
||||
return;
|
||||
}
|
||||
|
||||
var newDisplayName = organization.DisplayName();
|
||||
|
||||
// Organization.DisplayName() can return null - handle gracefully
|
||||
if (string.IsNullOrWhiteSpace(newDisplayName))
|
||||
{
|
||||
logger.LogWarning(
|
||||
"Organization ({OrganizationId}) has no name to update in Stripe",
|
||||
organization.Id);
|
||||
return;
|
||||
}
|
||||
|
||||
await stripeAdapter.CustomerUpdateAsync(organization.GatewayCustomerId,
|
||||
new CustomerUpdateOptions
|
||||
{
|
||||
@@ -197,9 +209,7 @@ public class OrganizationBillingService(
|
||||
new CustomerInvoiceSettingsCustomFieldOptions
|
||||
{
|
||||
Name = organization.SubscriberType(),
|
||||
Value = newDisplayName.Length <= 30
|
||||
? newDisplayName
|
||||
: newDisplayName[..30]
|
||||
Value = newDisplayName
|
||||
}]
|
||||
},
|
||||
});
|
||||
|
||||
@@ -113,4 +113,11 @@ public interface IProviderBillingService
|
||||
TaxInformation taxInformation);
|
||||
|
||||
Task UpdateSeatMinimums(UpdateProviderSeatMinimumsCommand command);
|
||||
|
||||
/// <summary>
|
||||
/// Updates the provider name and email on the Stripe customer entry.
|
||||
/// This only updates Stripe, not the Bitwarden database.
|
||||
/// </summary>
|
||||
/// <param name="provider">The provider to update in Stripe.</param>
|
||||
Task UpdateProviderNameAndEmail(Provider provider);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.Billing;
|
||||
using Bit.Core.Billing.Constants;
|
||||
using Bit.Core.Billing.Enums;
|
||||
using Bit.Core.Billing.Models.Sales;
|
||||
@@ -392,12 +391,13 @@ public class OrganizationBillingServiceTests
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task UpdateOrganizationNameAndEmail_WhenNameIsLong_TruncatesTo30Characters(
|
||||
public async Task UpdateOrganizationNameAndEmail_WhenNameIsLong_UsesFullName(
|
||||
Organization organization,
|
||||
SutProvider<OrganizationBillingService> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
organization.Name = "This is a very long organization name that exceeds thirty characters";
|
||||
var longName = "This is a very long organization name that exceeds thirty characters";
|
||||
organization.Name = longName;
|
||||
|
||||
CustomerUpdateOptions capturedOptions = null;
|
||||
sutProvider.GetDependency<IStripeAdapter>()
|
||||
@@ -421,14 +421,11 @@ public class OrganizationBillingServiceTests
|
||||
Assert.NotNull(capturedOptions.InvoiceSettings.CustomFields);
|
||||
|
||||
var customField = capturedOptions.InvoiceSettings.CustomFields.First();
|
||||
Assert.Equal(30, customField.Value.Length);
|
||||
|
||||
var expectedCustomFieldDisplayName = "This is a very long organizati";
|
||||
Assert.Equal(expectedCustomFieldDisplayName, customField.Value);
|
||||
Assert.Equal(longName, customField.Value);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task UpdateOrganizationNameAndEmail_WhenGatewayCustomerIdIsNull_ThrowsBillingException(
|
||||
public async Task UpdateOrganizationNameAndEmail_WhenGatewayCustomerIdIsNull_LogsWarningAndReturns(
|
||||
Organization organization,
|
||||
SutProvider<OrganizationBillingService> sutProvider)
|
||||
{
|
||||
@@ -436,15 +433,93 @@ public class OrganizationBillingServiceTests
|
||||
organization.GatewayCustomerId = null;
|
||||
organization.Name = "Test Organization";
|
||||
organization.BillingEmail = "billing@example.com";
|
||||
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
|
||||
|
||||
// Act & Assert
|
||||
var exception = await Assert.ThrowsAsync<BillingException>(
|
||||
() => sutProvider.Sut.UpdateOrganizationNameAndEmail(organization));
|
||||
// Act
|
||||
await sutProvider.Sut.UpdateOrganizationNameAndEmail(organization);
|
||||
|
||||
Assert.Contains("Cannot update an organization in Stripe without a GatewayCustomerId.", exception.Response);
|
||||
// Assert
|
||||
await stripeAdapter.DidNotReceive().CustomerUpdateAsync(
|
||||
Arg.Any<string>(),
|
||||
Arg.Any<CustomerUpdateOptions>());
|
||||
}
|
||||
|
||||
await sutProvider.GetDependency<IStripeAdapter>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.CustomerUpdateAsync(Arg.Any<string>(), Arg.Any<CustomerUpdateOptions>());
|
||||
[Theory, BitAutoData]
|
||||
public async Task UpdateOrganizationNameAndEmail_WhenGatewayCustomerIdIsEmpty_LogsWarningAndReturns(
|
||||
Organization organization,
|
||||
SutProvider<OrganizationBillingService> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
organization.GatewayCustomerId = "";
|
||||
organization.Name = "Test Organization";
|
||||
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
|
||||
|
||||
// Act
|
||||
await sutProvider.Sut.UpdateOrganizationNameAndEmail(organization);
|
||||
|
||||
// Assert
|
||||
await stripeAdapter.DidNotReceive().CustomerUpdateAsync(
|
||||
Arg.Any<string>(),
|
||||
Arg.Any<CustomerUpdateOptions>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task UpdateOrganizationNameAndEmail_WhenNameIsNull_LogsWarningAndReturns(
|
||||
Organization organization,
|
||||
SutProvider<OrganizationBillingService> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
organization.Name = null;
|
||||
organization.GatewayCustomerId = "cus_test123";
|
||||
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
|
||||
|
||||
// Act
|
||||
await sutProvider.Sut.UpdateOrganizationNameAndEmail(organization);
|
||||
|
||||
// Assert
|
||||
await stripeAdapter.DidNotReceive().CustomerUpdateAsync(
|
||||
Arg.Any<string>(),
|
||||
Arg.Any<CustomerUpdateOptions>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task UpdateOrganizationNameAndEmail_WhenNameIsEmpty_LogsWarningAndReturns(
|
||||
Organization organization,
|
||||
SutProvider<OrganizationBillingService> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
organization.Name = "";
|
||||
organization.GatewayCustomerId = "cus_test123";
|
||||
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
|
||||
|
||||
// Act
|
||||
await sutProvider.Sut.UpdateOrganizationNameAndEmail(organization);
|
||||
|
||||
// Assert
|
||||
await stripeAdapter.DidNotReceive().CustomerUpdateAsync(
|
||||
Arg.Any<string>(),
|
||||
Arg.Any<CustomerUpdateOptions>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task UpdateOrganizationNameAndEmail_WhenBillingEmailIsNull_UpdatesWithNull(
|
||||
Organization organization,
|
||||
SutProvider<OrganizationBillingService> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
organization.Name = "Test Organization";
|
||||
organization.BillingEmail = null;
|
||||
organization.GatewayCustomerId = "cus_test123";
|
||||
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
|
||||
|
||||
// Act
|
||||
await sutProvider.Sut.UpdateOrganizationNameAndEmail(organization);
|
||||
|
||||
// Assert
|
||||
await stripeAdapter.Received(1).CustomerUpdateAsync(
|
||||
organization.GatewayCustomerId,
|
||||
Arg.Is<CustomerUpdateOptions>(options =>
|
||||
options.Email == null &&
|
||||
options.Description == organization.Name));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user