mirror of
https://github.com/bitwarden/server
synced 2026-01-02 00:23:40 +00:00
Merge branch 'main' into auth/pm-22975/client-version-validator
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -234,6 +234,7 @@ bitwarden_license/src/Sso/Sso.zip
|
||||
/identity.json
|
||||
/api.json
|
||||
/api.public.json
|
||||
.serena/
|
||||
|
||||
# Serena
|
||||
.serena/
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
|
||||
<Version>2025.12.0</Version>
|
||||
<Version>2025.12.2</Version>
|
||||
|
||||
<RootNamespace>Bit.$(MSBuildProjectName)</RootNamespace>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
|
||||
@@ -113,7 +113,7 @@ public class RemoveOrganizationFromProviderCommand : IRemoveOrganizationFromProv
|
||||
await _providerBillingService.CreateCustomerForClientOrganization(provider, organization);
|
||||
}
|
||||
|
||||
var customer = await _stripeAdapter.CustomerUpdateAsync(organization.GatewayCustomerId, new CustomerUpdateOptions
|
||||
var customer = await _stripeAdapter.UpdateCustomerAsync(organization.GatewayCustomerId, new CustomerUpdateOptions
|
||||
{
|
||||
Description = string.Empty,
|
||||
Email = organization.BillingEmail,
|
||||
@@ -138,7 +138,7 @@ public class RemoveOrganizationFromProviderCommand : IRemoveOrganizationFromProv
|
||||
|
||||
subscriptionCreateOptions.AutomaticTax = new SubscriptionAutomaticTaxOptions { Enabled = true };
|
||||
|
||||
var subscription = await _stripeAdapter.SubscriptionCreateAsync(subscriptionCreateOptions);
|
||||
var subscription = await _stripeAdapter.CreateSubscriptionAsync(subscriptionCreateOptions);
|
||||
|
||||
organization.GatewaySubscriptionId = subscription.Id;
|
||||
organization.Status = OrganizationStatusType.Created;
|
||||
@@ -148,27 +148,26 @@ public class RemoveOrganizationFromProviderCommand : IRemoveOrganizationFromProv
|
||||
}
|
||||
else if (organization.IsStripeEnabled())
|
||||
{
|
||||
var subscription = await _stripeAdapter.SubscriptionGetAsync(organization.GatewaySubscriptionId, new SubscriptionGetOptions
|
||||
var subscription = await _stripeAdapter.GetSubscriptionAsync(organization.GatewaySubscriptionId, new SubscriptionGetOptions
|
||||
{
|
||||
Expand = ["customer"]
|
||||
});
|
||||
|
||||
if (subscription.Status is StripeConstants.SubscriptionStatus.Canceled or StripeConstants.SubscriptionStatus.IncompleteExpired)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await _stripeAdapter.CustomerUpdateAsync(subscription.CustomerId, new CustomerUpdateOptions
|
||||
await _stripeAdapter.UpdateCustomerAsync(subscription.CustomerId, new CustomerUpdateOptions
|
||||
{
|
||||
Email = organization.BillingEmail
|
||||
});
|
||||
|
||||
if (subscription.Customer.Discount?.Coupon != null)
|
||||
{
|
||||
await _stripeAdapter.CustomerDeleteDiscountAsync(subscription.CustomerId);
|
||||
await _stripeAdapter.DeleteCustomerDiscountAsync(subscription.CustomerId);
|
||||
}
|
||||
|
||||
await _stripeAdapter.SubscriptionUpdateAsync(organization.GatewaySubscriptionId, new SubscriptionUpdateOptions
|
||||
await _stripeAdapter.UpdateSubscriptionAsync(organization.GatewaySubscriptionId, new SubscriptionUpdateOptions
|
||||
{
|
||||
CollectionMethod = StripeConstants.CollectionMethod.SendInvoice,
|
||||
DaysUntilDue = 30,
|
||||
|
||||
@@ -15,6 +15,7 @@ using Bit.Core.Billing.Enums;
|
||||
using Bit.Core.Billing.Payment.Models;
|
||||
using Bit.Core.Billing.Pricing;
|
||||
using Bit.Core.Billing.Providers.Services;
|
||||
using Bit.Core.Billing.Services;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
@@ -427,7 +428,7 @@ public class ProviderService : IProviderService
|
||||
|
||||
if (!string.IsNullOrEmpty(organization.GatewayCustomerId))
|
||||
{
|
||||
await _stripeAdapter.CustomerUpdateAsync(organization.GatewayCustomerId, new CustomerUpdateOptions
|
||||
await _stripeAdapter.UpdateCustomerAsync(organization.GatewayCustomerId, new CustomerUpdateOptions
|
||||
{
|
||||
Email = provider.BillingEmail
|
||||
});
|
||||
@@ -487,7 +488,7 @@ public class ProviderService : IProviderService
|
||||
|
||||
private async Task<SubscriptionItem> GetSubscriptionItemAsync(string subscriptionId, string oldPlanId)
|
||||
{
|
||||
var subscriptionDetails = await _stripeAdapter.SubscriptionGetAsync(subscriptionId);
|
||||
var subscriptionDetails = await _stripeAdapter.GetSubscriptionAsync(subscriptionId);
|
||||
return subscriptionDetails.Items.Data.FirstOrDefault(item => item.Price.Id == oldPlanId);
|
||||
}
|
||||
|
||||
@@ -497,7 +498,7 @@ public class ProviderService : IProviderService
|
||||
{
|
||||
if (subscriptionItem.Price.Id != extractedPlanType)
|
||||
{
|
||||
await _stripeAdapter.SubscriptionUpdateAsync(subscriptionItem.Subscription,
|
||||
await _stripeAdapter.UpdateSubscriptionAsync(subscriptionItem.Subscription,
|
||||
new Stripe.SubscriptionUpdateOptions
|
||||
{
|
||||
Items = new List<Stripe.SubscriptionItemOptions>
|
||||
|
||||
@@ -4,7 +4,6 @@ using Bit.Core.Billing.Providers.Models;
|
||||
using Bit.Core.Billing.Providers.Queries;
|
||||
using Bit.Core.Billing.Services;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Services;
|
||||
using Stripe;
|
||||
using Stripe.Tax;
|
||||
|
||||
@@ -76,8 +75,8 @@ public class GetProviderWarningsQuery(
|
||||
|
||||
// Get active and scheduled registrations
|
||||
var registrations = (await Task.WhenAll(
|
||||
stripeAdapter.TaxRegistrationsListAsync(new RegistrationListOptions { Status = TaxRegistrationStatus.Active }),
|
||||
stripeAdapter.TaxRegistrationsListAsync(new RegistrationListOptions { Status = TaxRegistrationStatus.Scheduled })))
|
||||
stripeAdapter.ListTaxRegistrationsAsync(new RegistrationListOptions { Status = TaxRegistrationStatus.Active }),
|
||||
stripeAdapter.ListTaxRegistrationsAsync(new RegistrationListOptions { Status = TaxRegistrationStatus.Scheduled })))
|
||||
.SelectMany(registrations => registrations.Data);
|
||||
|
||||
// Find the matching registration for the customer
|
||||
|
||||
@@ -101,7 +101,7 @@ public class BusinessUnitConverter(
|
||||
providerUser.Status = ProviderUserStatusType.Confirmed;
|
||||
|
||||
// Stripe requires that we clear all the custom fields from the invoice settings if we want to replace them.
|
||||
await stripeAdapter.CustomerUpdateAsync(subscription.CustomerId, new CustomerUpdateOptions
|
||||
await stripeAdapter.UpdateCustomerAsync(subscription.CustomerId, new CustomerUpdateOptions
|
||||
{
|
||||
InvoiceSettings = new CustomerInvoiceSettingsOptions
|
||||
{
|
||||
@@ -116,7 +116,7 @@ public class BusinessUnitConverter(
|
||||
["convertedFrom"] = organization.Id.ToString()
|
||||
};
|
||||
|
||||
var updateCustomer = stripeAdapter.CustomerUpdateAsync(subscription.CustomerId, new CustomerUpdateOptions
|
||||
var updateCustomer = stripeAdapter.UpdateCustomerAsync(subscription.CustomerId, new CustomerUpdateOptions
|
||||
{
|
||||
InvoiceSettings = new CustomerInvoiceSettingsOptions
|
||||
{
|
||||
@@ -148,7 +148,7 @@ public class BusinessUnitConverter(
|
||||
|
||||
// Replace the existing password manager price with the new business unit price.
|
||||
var updateSubscription =
|
||||
stripeAdapter.SubscriptionUpdateAsync(subscription.Id,
|
||||
stripeAdapter.UpdateSubscriptionAsync(subscription.Id,
|
||||
new SubscriptionUpdateOptions
|
||||
{
|
||||
Items = [
|
||||
|
||||
@@ -61,11 +61,11 @@ public class ProviderBillingService(
|
||||
Organization organization,
|
||||
string key)
|
||||
{
|
||||
await stripeAdapter.SubscriptionUpdateAsync(organization.GatewaySubscriptionId,
|
||||
await stripeAdapter.UpdateSubscriptionAsync(organization.GatewaySubscriptionId,
|
||||
new SubscriptionUpdateOptions { CancelAtPeriodEnd = false });
|
||||
|
||||
var subscription =
|
||||
await stripeAdapter.SubscriptionCancelAsync(organization.GatewaySubscriptionId,
|
||||
await stripeAdapter.CancelSubscriptionAsync(organization.GatewaySubscriptionId,
|
||||
new SubscriptionCancelOptions
|
||||
{
|
||||
CancellationDetails = new SubscriptionCancellationDetailsOptions
|
||||
@@ -83,7 +83,7 @@ public class ProviderBillingService(
|
||||
|
||||
if (!wasTrialing && subscription.LatestInvoice.Status == InvoiceStatus.Draft)
|
||||
{
|
||||
await stripeAdapter.InvoiceFinalizeInvoiceAsync(subscription.LatestInvoiceId,
|
||||
await stripeAdapter.FinalizeInvoiceAsync(subscription.LatestInvoiceId,
|
||||
new InvoiceFinalizeOptions { AutoAdvance = true });
|
||||
}
|
||||
|
||||
@@ -138,7 +138,7 @@ public class ProviderBillingService(
|
||||
|
||||
if (clientCustomer.Balance != 0)
|
||||
{
|
||||
await stripeAdapter.CustomerBalanceTransactionCreate(provider.GatewayCustomerId,
|
||||
await stripeAdapter.CreateCustomerBalanceTransactionAsync(provider.GatewayCustomerId,
|
||||
new CustomerBalanceTransactionCreateOptions
|
||||
{
|
||||
Amount = clientCustomer.Balance,
|
||||
@@ -187,7 +187,7 @@ public class ProviderBillingService(
|
||||
]
|
||||
};
|
||||
|
||||
await stripeAdapter.SubscriptionUpdateAsync(provider.GatewaySubscriptionId, updateOptions);
|
||||
await stripeAdapter.UpdateSubscriptionAsync(provider.GatewaySubscriptionId, updateOptions);
|
||||
|
||||
// Refactor later to ?ChangeClientPlanCommand? (ProviderPlanId, ProviderId, OrganizationId)
|
||||
// 1. Retrieve PlanType and PlanName for ProviderPlan
|
||||
@@ -275,7 +275,7 @@ public class ProviderBillingService(
|
||||
customerCreateOptions.TaxExempt = TaxExempt.Reverse;
|
||||
}
|
||||
|
||||
var customer = await stripeAdapter.CustomerCreateAsync(customerCreateOptions);
|
||||
var customer = await stripeAdapter.CreateCustomerAsync(customerCreateOptions);
|
||||
|
||||
organization.GatewayCustomerId = customer.Id;
|
||||
|
||||
@@ -525,7 +525,7 @@ public class ProviderBillingService(
|
||||
case TokenizablePaymentMethodType.BankAccount:
|
||||
{
|
||||
var setupIntent =
|
||||
(await stripeAdapter.SetupIntentList(new SetupIntentListOptions
|
||||
(await stripeAdapter.ListSetupIntentsAsync(new SetupIntentListOptions
|
||||
{
|
||||
PaymentMethod = paymentMethod.Token
|
||||
}))
|
||||
@@ -558,7 +558,7 @@ public class ProviderBillingService(
|
||||
|
||||
try
|
||||
{
|
||||
return await stripeAdapter.CustomerCreateAsync(options);
|
||||
return await stripeAdapter.CreateCustomerAsync(options);
|
||||
}
|
||||
catch (StripeException stripeException) when (stripeException.StripeError?.Code == ErrorCodes.TaxIdInvalid)
|
||||
{
|
||||
@@ -580,7 +580,7 @@ public class ProviderBillingService(
|
||||
case TokenizablePaymentMethodType.BankAccount:
|
||||
{
|
||||
var setupIntentId = await setupIntentCache.GetSetupIntentIdForSubscriber(provider.Id);
|
||||
await stripeAdapter.SetupIntentCancel(setupIntentId,
|
||||
await stripeAdapter.CancelSetupIntentAsync(setupIntentId,
|
||||
new SetupIntentCancelOptions { CancellationReason = "abandoned" });
|
||||
await setupIntentCache.RemoveSetupIntentForSubscriber(provider.Id);
|
||||
break;
|
||||
@@ -638,7 +638,7 @@ public class ProviderBillingService(
|
||||
var setupIntentId = await setupIntentCache.GetSetupIntentIdForSubscriber(provider.Id);
|
||||
|
||||
var setupIntent = !string.IsNullOrEmpty(setupIntentId)
|
||||
? await stripeAdapter.SetupIntentGet(setupIntentId,
|
||||
? await stripeAdapter.GetSetupIntentAsync(setupIntentId,
|
||||
new SetupIntentGetOptions { Expand = ["payment_method"] })
|
||||
: null;
|
||||
|
||||
@@ -673,7 +673,7 @@ public class ProviderBillingService(
|
||||
|
||||
try
|
||||
{
|
||||
var subscription = await stripeAdapter.SubscriptionCreateAsync(subscriptionCreateOptions);
|
||||
var subscription = await stripeAdapter.CreateSubscriptionAsync(subscriptionCreateOptions);
|
||||
|
||||
if (subscription is
|
||||
{
|
||||
@@ -708,7 +708,7 @@ public class ProviderBillingService(
|
||||
subscriberService.UpdatePaymentSource(provider, tokenizedPaymentSource),
|
||||
subscriberService.UpdateTaxInformation(provider, taxInformation));
|
||||
|
||||
await stripeAdapter.SubscriptionUpdateAsync(provider.GatewaySubscriptionId,
|
||||
await stripeAdapter.UpdateSubscriptionAsync(provider.GatewaySubscriptionId,
|
||||
new SubscriptionUpdateOptions { CollectionMethod = CollectionMethod.ChargeAutomatically });
|
||||
}
|
||||
|
||||
@@ -791,7 +791,7 @@ public class ProviderBillingService(
|
||||
|
||||
if (subscriptionItemOptionsList.Count > 0)
|
||||
{
|
||||
await stripeAdapter.SubscriptionUpdateAsync(provider.GatewaySubscriptionId,
|
||||
await stripeAdapter.UpdateSubscriptionAsync(provider.GatewaySubscriptionId,
|
||||
new SubscriptionUpdateOptions { Items = subscriptionItemOptionsList });
|
||||
}
|
||||
}
|
||||
@@ -807,7 +807,7 @@ public class ProviderBillingService(
|
||||
|
||||
var item = subscription.Items.First(item => item.Price.Id == priceId);
|
||||
|
||||
await stripeAdapter.SubscriptionUpdateAsync(provider.GatewaySubscriptionId, new SubscriptionUpdateOptions
|
||||
await stripeAdapter.UpdateSubscriptionAsync(provider.GatewaySubscriptionId, new SubscriptionUpdateOptions
|
||||
{
|
||||
Items =
|
||||
[
|
||||
|
||||
@@ -8,6 +8,7 @@ using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.E
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Models;
|
||||
using Bit.Core.AdminConsole.Utilities.Commands;
|
||||
using Bit.Core.Billing.Pricing;
|
||||
using Bit.Core.Billing.Services;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
|
||||
@@ -24,7 +25,7 @@ public class PostUserCommand(
|
||||
IOrganizationRepository organizationRepository,
|
||||
IOrganizationUserRepository organizationUserRepository,
|
||||
IOrganizationService organizationService,
|
||||
IPaymentService paymentService,
|
||||
IStripePaymentService paymentService,
|
||||
IScimContext scimContext,
|
||||
IFeatureService featureService,
|
||||
IInviteOrganizationUsersCommand inviteOrganizationUsersCommand,
|
||||
|
||||
@@ -201,12 +201,15 @@ public class AccountController : Controller
|
||||
returnUrl,
|
||||
state = context.Parameters["state"],
|
||||
userIdentifier = context.Parameters["session_state"],
|
||||
ssoToken
|
||||
});
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public IActionResult ExternalChallenge(string scheme, string returnUrl, string state, string userIdentifier)
|
||||
public IActionResult ExternalChallenge(string scheme, string returnUrl, string state, string userIdentifier, string ssoToken)
|
||||
{
|
||||
ValidateSchemeAgainstSsoToken(scheme, ssoToken);
|
||||
|
||||
if (string.IsNullOrEmpty(returnUrl))
|
||||
{
|
||||
returnUrl = "~/";
|
||||
@@ -235,6 +238,31 @@ public class AccountController : Controller
|
||||
return Challenge(props, scheme);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates the scheme (organization ID) against the organization ID found in the ssoToken.
|
||||
/// </summary>
|
||||
/// <param name="scheme">The authentication scheme (organization ID) to validate.</param>
|
||||
/// <param name="ssoToken">The SSO token to validate against.</param>
|
||||
/// <exception cref="Exception">Thrown if the scheme (organization ID) does not match the organization ID found in the ssoToken.</exception>
|
||||
private void ValidateSchemeAgainstSsoToken(string scheme, string ssoToken)
|
||||
{
|
||||
SsoTokenable tokenable;
|
||||
|
||||
try
|
||||
{
|
||||
tokenable = _dataProtector.Unprotect(ssoToken);
|
||||
}
|
||||
catch
|
||||
{
|
||||
throw new Exception(_i18nService.T("InvalidSsoToken"));
|
||||
}
|
||||
|
||||
if (!Guid.TryParse(scheme, out var schemeOrgId) || tokenable.OrganizationId != schemeOrgId)
|
||||
{
|
||||
throw new Exception(_i18nService.T("SsoOrganizationIdMismatch"));
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> ExternalCallback()
|
||||
{
|
||||
|
||||
@@ -131,7 +131,7 @@ public class RemoveOrganizationFromProviderCommandTests
|
||||
Arg.Is<IEnumerable<string>>(emails => emails.FirstOrDefault() == "a@example.com"));
|
||||
|
||||
await sutProvider.GetDependency<IStripeAdapter>().DidNotReceiveWithAnyArgs()
|
||||
.CustomerUpdateAsync(Arg.Any<string>(), Arg.Any<CustomerUpdateOptions>());
|
||||
.UpdateCustomerAsync(Arg.Any<string>(), Arg.Any<CustomerUpdateOptions>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
@@ -156,7 +156,7 @@ public class RemoveOrganizationFromProviderCommandTests
|
||||
"b@example.com"
|
||||
]);
|
||||
|
||||
sutProvider.GetDependency<IStripeAdapter>().SubscriptionGetAsync(organization.GatewaySubscriptionId, Arg.Is<SubscriptionGetOptions>(
|
||||
sutProvider.GetDependency<IStripeAdapter>().GetSubscriptionAsync(organization.GatewaySubscriptionId, Arg.Is<SubscriptionGetOptions>(
|
||||
options => options.Expand.Contains("customer")))
|
||||
.Returns(GetSubscription(organization.GatewaySubscriptionId, organization.GatewayCustomerId));
|
||||
|
||||
@@ -164,12 +164,14 @@ public class RemoveOrganizationFromProviderCommandTests
|
||||
|
||||
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
|
||||
|
||||
await stripeAdapter.Received(1).CustomerUpdateAsync(organization.GatewayCustomerId,
|
||||
await stripeAdapter.Received(1).UpdateCustomerAsync(organization.GatewayCustomerId,
|
||||
Arg.Is<CustomerUpdateOptions>(options => options.Email == "a@example.com"));
|
||||
|
||||
await stripeAdapter.Received(1).CustomerDeleteDiscountAsync(organization.GatewayCustomerId);
|
||||
await stripeAdapter.Received(1).DeleteCustomerDiscountAsync(organization.GatewayCustomerId);
|
||||
|
||||
await stripeAdapter.Received(1).SubscriptionUpdateAsync(organization.GatewaySubscriptionId,
|
||||
await stripeAdapter.Received(1).DeleteCustomerDiscountAsync(organization.GatewayCustomerId);
|
||||
|
||||
await stripeAdapter.Received(1).UpdateSubscriptionAsync(organization.GatewaySubscriptionId,
|
||||
Arg.Is<SubscriptionUpdateOptions>(options =>
|
||||
options.CollectionMethod == StripeConstants.CollectionMethod.SendInvoice &&
|
||||
options.DaysUntilDue == 30));
|
||||
@@ -226,7 +228,7 @@ public class RemoveOrganizationFromProviderCommandTests
|
||||
|
||||
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
|
||||
|
||||
stripeAdapter.CustomerUpdateAsync(organization.GatewayCustomerId, Arg.Is<CustomerUpdateOptions>(options =>
|
||||
stripeAdapter.UpdateCustomerAsync(organization.GatewayCustomerId, Arg.Is<CustomerUpdateOptions>(options =>
|
||||
options.Description == string.Empty &&
|
||||
options.Email == organization.BillingEmail &&
|
||||
options.Expand[0] == "tax" &&
|
||||
@@ -239,14 +241,14 @@ public class RemoveOrganizationFromProviderCommandTests
|
||||
}
|
||||
});
|
||||
|
||||
stripeAdapter.SubscriptionCreateAsync(Arg.Any<SubscriptionCreateOptions>()).Returns(new Subscription
|
||||
stripeAdapter.CreateSubscriptionAsync(Arg.Any<SubscriptionCreateOptions>()).Returns(new Subscription
|
||||
{
|
||||
Id = "subscription_id"
|
||||
});
|
||||
|
||||
await sutProvider.Sut.RemoveOrganizationFromProvider(provider, providerOrganization, organization);
|
||||
|
||||
await stripeAdapter.Received(1).SubscriptionCreateAsync(Arg.Is<SubscriptionCreateOptions>(options =>
|
||||
await stripeAdapter.Received(1).CreateSubscriptionAsync(Arg.Is<SubscriptionCreateOptions>(options =>
|
||||
options.Customer == organization.GatewayCustomerId &&
|
||||
options.CollectionMethod == StripeConstants.CollectionMethod.SendInvoice &&
|
||||
options.DaysUntilDue == 30 &&
|
||||
@@ -315,7 +317,7 @@ public class RemoveOrganizationFromProviderCommandTests
|
||||
|
||||
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
|
||||
|
||||
stripeAdapter.CustomerUpdateAsync(organization.GatewayCustomerId, Arg.Is<CustomerUpdateOptions>(options =>
|
||||
stripeAdapter.UpdateCustomerAsync(organization.GatewayCustomerId, Arg.Is<CustomerUpdateOptions>(options =>
|
||||
options.Description == string.Empty &&
|
||||
options.Email == organization.BillingEmail &&
|
||||
options.Expand[0] == "tax" &&
|
||||
@@ -328,14 +330,14 @@ public class RemoveOrganizationFromProviderCommandTests
|
||||
}
|
||||
});
|
||||
|
||||
stripeAdapter.SubscriptionCreateAsync(Arg.Any<SubscriptionCreateOptions>()).Returns(new Subscription
|
||||
stripeAdapter.CreateSubscriptionAsync(Arg.Any<SubscriptionCreateOptions>()).Returns(new Subscription
|
||||
{
|
||||
Id = "subscription_id"
|
||||
});
|
||||
|
||||
await sutProvider.Sut.RemoveOrganizationFromProvider(provider, providerOrganization, organization);
|
||||
|
||||
await stripeAdapter.Received(1).SubscriptionCreateAsync(Arg.Is<SubscriptionCreateOptions>(options =>
|
||||
await stripeAdapter.Received(1).CreateSubscriptionAsync(Arg.Is<SubscriptionCreateOptions>(options =>
|
||||
options.Customer == organization.GatewayCustomerId &&
|
||||
options.CollectionMethod == StripeConstants.CollectionMethod.SendInvoice &&
|
||||
options.DaysUntilDue == 30 &&
|
||||
@@ -434,7 +436,7 @@ public class RemoveOrganizationFromProviderCommandTests
|
||||
|
||||
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
|
||||
|
||||
stripeAdapter.CustomerUpdateAsync(organization.GatewayCustomerId, Arg.Any<CustomerUpdateOptions>())
|
||||
stripeAdapter.UpdateCustomerAsync(organization.GatewayCustomerId, Arg.Any<CustomerUpdateOptions>())
|
||||
.Returns(new Customer
|
||||
{
|
||||
Id = "customer_id",
|
||||
@@ -444,7 +446,7 @@ public class RemoveOrganizationFromProviderCommandTests
|
||||
}
|
||||
});
|
||||
|
||||
stripeAdapter.SubscriptionCreateAsync(Arg.Any<SubscriptionCreateOptions>()).Returns(new Subscription
|
||||
stripeAdapter.CreateSubscriptionAsync(Arg.Any<SubscriptionCreateOptions>()).Returns(new Subscription
|
||||
{
|
||||
Id = "new_subscription_id"
|
||||
});
|
||||
|
||||
@@ -12,6 +12,7 @@ using Bit.Core.Billing.Enums;
|
||||
using Bit.Core.Billing.Payment.Models;
|
||||
using Bit.Core.Billing.Pricing;
|
||||
using Bit.Core.Billing.Providers.Services;
|
||||
using Bit.Core.Billing.Services;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
@@ -757,7 +758,7 @@ public class ProviderServiceTests
|
||||
await organizationRepository.Received(1)
|
||||
.ReplaceAsync(Arg.Is<Organization>(org => org.BillingEmail == provider.BillingEmail));
|
||||
|
||||
await sutProvider.GetDependency<IStripeAdapter>().Received(1).CustomerUpdateAsync(
|
||||
await sutProvider.GetDependency<IStripeAdapter>().Received(1).UpdateCustomerAsync(
|
||||
organization.GatewayCustomerId,
|
||||
Arg.Is<CustomerUpdateOptions>(options => options.Email == provider.BillingEmail));
|
||||
|
||||
@@ -828,9 +829,9 @@ public class ProviderServiceTests
|
||||
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
|
||||
var subscriptionItem = GetSubscription(organization.GatewaySubscriptionId);
|
||||
sutProvider.GetDependency<IStripeAdapter>().SubscriptionGetAsync(organization.GatewaySubscriptionId)
|
||||
sutProvider.GetDependency<IStripeAdapter>().GetSubscriptionAsync(organization.GatewaySubscriptionId)
|
||||
.Returns(GetSubscription(organization.GatewaySubscriptionId));
|
||||
await sutProvider.GetDependency<IStripeAdapter>().SubscriptionUpdateAsync(
|
||||
await sutProvider.GetDependency<IStripeAdapter>().UpdateSubscriptionAsync(
|
||||
organization.GatewaySubscriptionId, SubscriptionUpdateRequest(expectedPlanId, subscriptionItem));
|
||||
|
||||
await sutProvider.Sut.AddOrganization(provider.Id, organization.Id, key);
|
||||
|
||||
@@ -3,7 +3,6 @@ using Bit.Core.AdminConsole.Entities.Provider;
|
||||
using Bit.Core.Billing.Constants;
|
||||
using Bit.Core.Billing.Services;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
@@ -63,7 +62,7 @@ public class GetProviderWarningsQueryTests
|
||||
});
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>().ProviderProviderAdmin(provider.Id).Returns(true);
|
||||
sutProvider.GetDependency<IStripeAdapter>().TaxRegistrationsListAsync(Arg.Any<RegistrationListOptions>())
|
||||
sutProvider.GetDependency<IStripeAdapter>().ListTaxRegistrationsAsync(Arg.Any<RegistrationListOptions>())
|
||||
.Returns(new StripeList<Registration> { Data = [] });
|
||||
|
||||
var response = await sutProvider.Sut.Run(provider);
|
||||
@@ -95,7 +94,7 @@ public class GetProviderWarningsQueryTests
|
||||
});
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>().ProviderProviderAdmin(provider.Id).Returns(true);
|
||||
sutProvider.GetDependency<IStripeAdapter>().TaxRegistrationsListAsync(Arg.Any<RegistrationListOptions>())
|
||||
sutProvider.GetDependency<IStripeAdapter>().ListTaxRegistrationsAsync(Arg.Any<RegistrationListOptions>())
|
||||
.Returns(new StripeList<Registration> { Data = [] });
|
||||
|
||||
var response = await sutProvider.Sut.Run(provider);
|
||||
@@ -129,7 +128,7 @@ public class GetProviderWarningsQueryTests
|
||||
});
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>().ProviderProviderAdmin(provider.Id).Returns(false);
|
||||
sutProvider.GetDependency<IStripeAdapter>().TaxRegistrationsListAsync(Arg.Any<RegistrationListOptions>())
|
||||
sutProvider.GetDependency<IStripeAdapter>().ListTaxRegistrationsAsync(Arg.Any<RegistrationListOptions>())
|
||||
.Returns(new StripeList<Registration> { Data = [] });
|
||||
|
||||
var response = await sutProvider.Sut.Run(provider);
|
||||
@@ -163,7 +162,7 @@ public class GetProviderWarningsQueryTests
|
||||
});
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>().ProviderProviderAdmin(provider.Id).Returns(true);
|
||||
sutProvider.GetDependency<IStripeAdapter>().TaxRegistrationsListAsync(Arg.Any<RegistrationListOptions>())
|
||||
sutProvider.GetDependency<IStripeAdapter>().ListTaxRegistrationsAsync(Arg.Any<RegistrationListOptions>())
|
||||
.Returns(new StripeList<Registration> { Data = [] });
|
||||
|
||||
var response = await sutProvider.Sut.Run(provider);
|
||||
@@ -224,7 +223,7 @@ public class GetProviderWarningsQueryTests
|
||||
});
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>().ProviderProviderAdmin(provider.Id).Returns(true);
|
||||
sutProvider.GetDependency<IStripeAdapter>().TaxRegistrationsListAsync(Arg.Any<RegistrationListOptions>())
|
||||
sutProvider.GetDependency<IStripeAdapter>().ListTaxRegistrationsAsync(Arg.Any<RegistrationListOptions>())
|
||||
.Returns(new StripeList<Registration>
|
||||
{
|
||||
Data = [new Registration { Country = "GB" }]
|
||||
@@ -257,7 +256,7 @@ public class GetProviderWarningsQueryTests
|
||||
});
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>().ProviderProviderAdmin(provider.Id).Returns(true);
|
||||
sutProvider.GetDependency<IStripeAdapter>().TaxRegistrationsListAsync(Arg.Any<RegistrationListOptions>())
|
||||
sutProvider.GetDependency<IStripeAdapter>().ListTaxRegistrationsAsync(Arg.Any<RegistrationListOptions>())
|
||||
.Returns(new StripeList<Registration>
|
||||
{
|
||||
Data = [new Registration { Country = "CA" }]
|
||||
@@ -296,7 +295,7 @@ public class GetProviderWarningsQueryTests
|
||||
});
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>().ProviderProviderAdmin(provider.Id).Returns(true);
|
||||
sutProvider.GetDependency<IStripeAdapter>().TaxRegistrationsListAsync(Arg.Any<RegistrationListOptions>())
|
||||
sutProvider.GetDependency<IStripeAdapter>().ListTaxRegistrationsAsync(Arg.Any<RegistrationListOptions>())
|
||||
.Returns(new StripeList<Registration>
|
||||
{
|
||||
Data = [new Registration { Country = "CA" }]
|
||||
@@ -338,7 +337,7 @@ public class GetProviderWarningsQueryTests
|
||||
});
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>().ProviderProviderAdmin(provider.Id).Returns(true);
|
||||
sutProvider.GetDependency<IStripeAdapter>().TaxRegistrationsListAsync(Arg.Any<RegistrationListOptions>())
|
||||
sutProvider.GetDependency<IStripeAdapter>().ListTaxRegistrationsAsync(Arg.Any<RegistrationListOptions>())
|
||||
.Returns(new StripeList<Registration>
|
||||
{
|
||||
Data = [new Registration { Country = "CA" }]
|
||||
@@ -383,7 +382,7 @@ public class GetProviderWarningsQueryTests
|
||||
});
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>().ProviderProviderAdmin(provider.Id).Returns(true);
|
||||
sutProvider.GetDependency<IStripeAdapter>().TaxRegistrationsListAsync(Arg.Any<RegistrationListOptions>())
|
||||
sutProvider.GetDependency<IStripeAdapter>().ListTaxRegistrationsAsync(Arg.Any<RegistrationListOptions>())
|
||||
.Returns(new StripeList<Registration>
|
||||
{
|
||||
Data = [new Registration { Country = "CA" }]
|
||||
@@ -428,7 +427,7 @@ public class GetProviderWarningsQueryTests
|
||||
});
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>().ProviderProviderAdmin(provider.Id).Returns(true);
|
||||
sutProvider.GetDependency<IStripeAdapter>().TaxRegistrationsListAsync(Arg.Any<RegistrationListOptions>())
|
||||
sutProvider.GetDependency<IStripeAdapter>().ListTaxRegistrationsAsync(Arg.Any<RegistrationListOptions>())
|
||||
.Returns(new StripeList<Registration>
|
||||
{
|
||||
Data = [new Registration { Country = "CA" }]
|
||||
@@ -461,7 +460,7 @@ public class GetProviderWarningsQueryTests
|
||||
});
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>().ProviderProviderAdmin(provider.Id).Returns(true);
|
||||
sutProvider.GetDependency<IStripeAdapter>().TaxRegistrationsListAsync(Arg.Is<RegistrationListOptions>(opt => opt.Status == TaxRegistrationStatus.Active))
|
||||
sutProvider.GetDependency<IStripeAdapter>().ListTaxRegistrationsAsync(Arg.Is<RegistrationListOptions>(opt => opt.Status == TaxRegistrationStatus.Active))
|
||||
.Returns(new StripeList<Registration>
|
||||
{
|
||||
Data = [
|
||||
@@ -470,7 +469,7 @@ public class GetProviderWarningsQueryTests
|
||||
new Registration { Country = "FR" }
|
||||
]
|
||||
});
|
||||
sutProvider.GetDependency<IStripeAdapter>().TaxRegistrationsListAsync(Arg.Is<RegistrationListOptions>(opt => opt.Status == TaxRegistrationStatus.Scheduled))
|
||||
sutProvider.GetDependency<IStripeAdapter>().ListTaxRegistrationsAsync(Arg.Is<RegistrationListOptions>(opt => opt.Status == TaxRegistrationStatus.Scheduled))
|
||||
.Returns(new StripeList<Registration> { Data = [] });
|
||||
|
||||
var response = await sutProvider.Sut.Run(provider);
|
||||
@@ -505,7 +504,7 @@ public class GetProviderWarningsQueryTests
|
||||
});
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>().ProviderProviderAdmin(provider.Id).Returns(true);
|
||||
sutProvider.GetDependency<IStripeAdapter>().TaxRegistrationsListAsync(Arg.Any<RegistrationListOptions>())
|
||||
sutProvider.GetDependency<IStripeAdapter>().ListTaxRegistrationsAsync(Arg.Any<RegistrationListOptions>())
|
||||
.Returns(new StripeList<Registration>
|
||||
{
|
||||
Data = [new Registration { Country = "CA" }]
|
||||
@@ -543,7 +542,7 @@ public class GetProviderWarningsQueryTests
|
||||
});
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>().ProviderProviderAdmin(provider.Id).Returns(true);
|
||||
sutProvider.GetDependency<IStripeAdapter>().TaxRegistrationsListAsync(Arg.Any<RegistrationListOptions>())
|
||||
sutProvider.GetDependency<IStripeAdapter>().ListTaxRegistrationsAsync(Arg.Any<RegistrationListOptions>())
|
||||
.Returns(new StripeList<Registration>
|
||||
{
|
||||
Data = [new Registration { Country = "US" }]
|
||||
|
||||
@@ -144,11 +144,11 @@ public class BusinessUnitConverterTests
|
||||
|
||||
await businessUnitConverter.FinalizeConversion(organization, userId, token, providerKey, organizationKey);
|
||||
|
||||
await _stripeAdapter.Received(2).CustomerUpdateAsync(subscription.CustomerId, Arg.Any<CustomerUpdateOptions>());
|
||||
await _stripeAdapter.Received(2).UpdateCustomerAsync(subscription.CustomerId, Arg.Any<CustomerUpdateOptions>());
|
||||
|
||||
var updatedPriceId = ProviderPriceAdapter.GetActivePriceId(provider, enterpriseAnnually.Type);
|
||||
|
||||
await _stripeAdapter.Received(1).SubscriptionUpdateAsync(subscription.Id, Arg.Is<SubscriptionUpdateOptions>(
|
||||
await _stripeAdapter.Received(1).UpdateSubscriptionAsync(subscription.Id, Arg.Is<SubscriptionUpdateOptions>(
|
||||
arguments =>
|
||||
arguments.Items.Count == 2 &&
|
||||
arguments.Items[0].Id == "subscription_item_id" &&
|
||||
|
||||
@@ -20,7 +20,6 @@ using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Settings;
|
||||
using Bit.Core.Test.Billing.Mocks;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
@@ -85,7 +84,7 @@ public class ProviderBillingServiceTests
|
||||
|
||||
// Assert
|
||||
await providerPlanRepository.Received(0).ReplaceAsync(Arg.Any<ProviderPlan>());
|
||||
await stripeAdapter.Received(0).SubscriptionUpdateAsync(Arg.Any<string>(), Arg.Any<SubscriptionUpdateOptions>());
|
||||
await stripeAdapter.Received(0).UpdateSubscriptionAsync(Arg.Any<string>(), Arg.Any<SubscriptionUpdateOptions>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
@@ -113,7 +112,7 @@ public class ProviderBillingServiceTests
|
||||
|
||||
// Assert
|
||||
await providerPlanRepository.Received(0).ReplaceAsync(Arg.Any<ProviderPlan>());
|
||||
await stripeAdapter.Received(0).SubscriptionUpdateAsync(Arg.Any<string>(), Arg.Any<SubscriptionUpdateOptions>());
|
||||
await stripeAdapter.Received(0).UpdateSubscriptionAsync(Arg.Any<string>(), Arg.Any<SubscriptionUpdateOptions>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
@@ -180,14 +179,14 @@ public class ProviderBillingServiceTests
|
||||
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
|
||||
|
||||
await stripeAdapter.Received(1)
|
||||
.SubscriptionUpdateAsync(
|
||||
.UpdateSubscriptionAsync(
|
||||
Arg.Is(provider.GatewaySubscriptionId),
|
||||
Arg.Is<SubscriptionUpdateOptions>(p =>
|
||||
p.Items.Count(si => si.Id == "si_ent_annual" && si.Deleted == true) == 1));
|
||||
|
||||
var newPlanCfg = MockPlans.Get(command.NewPlan);
|
||||
await stripeAdapter.Received(1)
|
||||
.SubscriptionUpdateAsync(
|
||||
.UpdateSubscriptionAsync(
|
||||
Arg.Is(provider.GatewaySubscriptionId),
|
||||
Arg.Is<SubscriptionUpdateOptions>(p =>
|
||||
p.Items.Count(si =>
|
||||
@@ -268,7 +267,7 @@ public class ProviderBillingServiceTests
|
||||
CloudRegion = "US"
|
||||
});
|
||||
|
||||
sutProvider.GetDependency<IStripeAdapter>().CustomerCreateAsync(Arg.Is<CustomerCreateOptions>(
|
||||
sutProvider.GetDependency<IStripeAdapter>().CreateCustomerAsync(Arg.Is<CustomerCreateOptions>(
|
||||
options =>
|
||||
options.Address.Country == providerCustomer.Address.Country &&
|
||||
options.Address.PostalCode == providerCustomer.Address.PostalCode &&
|
||||
@@ -288,7 +287,7 @@ public class ProviderBillingServiceTests
|
||||
|
||||
await sutProvider.Sut.CreateCustomerForClientOrganization(provider, organization);
|
||||
|
||||
await sutProvider.GetDependency<IStripeAdapter>().Received(1).CustomerCreateAsync(Arg.Is<CustomerCreateOptions>(
|
||||
await sutProvider.GetDependency<IStripeAdapter>().Received(1).CreateCustomerAsync(Arg.Is<CustomerCreateOptions>(
|
||||
options =>
|
||||
options.Address.Country == providerCustomer.Address.Country &&
|
||||
options.Address.PostalCode == providerCustomer.Address.PostalCode &&
|
||||
@@ -349,7 +348,7 @@ public class ProviderBillingServiceTests
|
||||
CloudRegion = "US"
|
||||
});
|
||||
|
||||
sutProvider.GetDependency<IStripeAdapter>().CustomerCreateAsync(Arg.Is<CustomerCreateOptions>(
|
||||
sutProvider.GetDependency<IStripeAdapter>().CreateCustomerAsync(Arg.Is<CustomerCreateOptions>(
|
||||
options =>
|
||||
options.Address.Country == providerCustomer.Address.Country &&
|
||||
options.Address.PostalCode == providerCustomer.Address.PostalCode &&
|
||||
@@ -370,7 +369,7 @@ public class ProviderBillingServiceTests
|
||||
|
||||
await sutProvider.Sut.CreateCustomerForClientOrganization(provider, organization);
|
||||
|
||||
await sutProvider.GetDependency<IStripeAdapter>().Received(1).CustomerCreateAsync(Arg.Is<CustomerCreateOptions>(
|
||||
await sutProvider.GetDependency<IStripeAdapter>().Received(1).CreateCustomerAsync(Arg.Is<CustomerCreateOptions>(
|
||||
options =>
|
||||
options.Address.Country == providerCustomer.Address.Country &&
|
||||
options.Address.PostalCode == providerCustomer.Address.PostalCode &&
|
||||
@@ -535,7 +534,7 @@ public class ProviderBillingServiceTests
|
||||
await sutProvider.Sut.ScaleSeats(provider, PlanType.TeamsMonthly, 10);
|
||||
|
||||
// 50 assigned seats + 10 seat scale up = 60 seats, well below the 100 minimum
|
||||
await sutProvider.GetDependency<IStripeAdapter>().DidNotReceiveWithAnyArgs().SubscriptionUpdateAsync(
|
||||
await sutProvider.GetDependency<IStripeAdapter>().DidNotReceiveWithAnyArgs().UpdateSubscriptionAsync(
|
||||
Arg.Any<string>(),
|
||||
Arg.Any<SubscriptionUpdateOptions>());
|
||||
|
||||
@@ -619,7 +618,7 @@ public class ProviderBillingServiceTests
|
||||
await sutProvider.Sut.ScaleSeats(provider, PlanType.TeamsMonthly, 10);
|
||||
|
||||
// 95 current + 10 seat scale = 105 seats, 5 above the minimum
|
||||
await sutProvider.GetDependency<IStripeAdapter>().Received(1).SubscriptionUpdateAsync(
|
||||
await sutProvider.GetDependency<IStripeAdapter>().Received(1).UpdateSubscriptionAsync(
|
||||
provider.GatewaySubscriptionId,
|
||||
Arg.Is<SubscriptionUpdateOptions>(
|
||||
options =>
|
||||
@@ -707,7 +706,7 @@ public class ProviderBillingServiceTests
|
||||
await sutProvider.Sut.ScaleSeats(provider, PlanType.TeamsMonthly, 10);
|
||||
|
||||
// 110 current + 10 seat scale up = 120 seats
|
||||
await sutProvider.GetDependency<IStripeAdapter>().Received(1).SubscriptionUpdateAsync(
|
||||
await sutProvider.GetDependency<IStripeAdapter>().Received(1).UpdateSubscriptionAsync(
|
||||
provider.GatewaySubscriptionId,
|
||||
Arg.Is<SubscriptionUpdateOptions>(
|
||||
options =>
|
||||
@@ -795,7 +794,7 @@ public class ProviderBillingServiceTests
|
||||
await sutProvider.Sut.ScaleSeats(provider, PlanType.TeamsMonthly, -30);
|
||||
|
||||
// 110 seats - 30 scale down seats = 80 seats, below the 100 seat minimum.
|
||||
await sutProvider.GetDependency<IStripeAdapter>().Received(1).SubscriptionUpdateAsync(
|
||||
await sutProvider.GetDependency<IStripeAdapter>().Received(1).UpdateSubscriptionAsync(
|
||||
provider.GatewaySubscriptionId,
|
||||
Arg.Is<SubscriptionUpdateOptions>(
|
||||
options =>
|
||||
@@ -914,12 +913,12 @@ public class ProviderBillingServiceTests
|
||||
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
|
||||
var tokenizedPaymentMethod = new TokenizedPaymentMethod { Type = TokenizablePaymentMethodType.BankAccount, Token = "token" };
|
||||
|
||||
stripeAdapter.SetupIntentList(Arg.Is<SetupIntentListOptions>(options =>
|
||||
stripeAdapter.ListSetupIntentsAsync(Arg.Is<SetupIntentListOptions>(options =>
|
||||
options.PaymentMethod == tokenizedPaymentMethod.Token)).Returns([
|
||||
new SetupIntent { Id = "setup_intent_id" }
|
||||
]);
|
||||
|
||||
stripeAdapter.CustomerCreateAsync(Arg.Is<CustomerCreateOptions>(o =>
|
||||
stripeAdapter.CreateCustomerAsync(Arg.Is<CustomerCreateOptions>(o =>
|
||||
o.Address.Country == billingAddress.Country &&
|
||||
o.Address.PostalCode == billingAddress.PostalCode &&
|
||||
o.Address.Line1 == billingAddress.Line1 &&
|
||||
@@ -942,7 +941,7 @@ public class ProviderBillingServiceTests
|
||||
|
||||
await sutProvider.GetDependency<ISetupIntentCache>().Received(1).Set(provider.Id, "setup_intent_id");
|
||||
|
||||
await stripeAdapter.Received(1).SetupIntentCancel("setup_intent_id", Arg.Is<SetupIntentCancelOptions>(options =>
|
||||
await stripeAdapter.Received(1).CancelSetupIntentAsync("setup_intent_id", Arg.Is<SetupIntentCancelOptions>(options =>
|
||||
options.CancellationReason == "abandoned"));
|
||||
|
||||
await sutProvider.GetDependency<ISetupIntentCache>().Received(1).RemoveSetupIntentForSubscriber(provider.Id);
|
||||
@@ -964,7 +963,7 @@ public class ProviderBillingServiceTests
|
||||
sutProvider.GetDependency<ISubscriberService>().CreateBraintreeCustomer(provider, tokenizedPaymentMethod.Token)
|
||||
.Returns("braintree_customer_id");
|
||||
|
||||
stripeAdapter.CustomerCreateAsync(Arg.Is<CustomerCreateOptions>(o =>
|
||||
stripeAdapter.CreateCustomerAsync(Arg.Is<CustomerCreateOptions>(o =>
|
||||
o.Address.Country == billingAddress.Country &&
|
||||
o.Address.PostalCode == billingAddress.PostalCode &&
|
||||
o.Address.Line1 == billingAddress.Line1 &&
|
||||
@@ -1007,12 +1006,12 @@ public class ProviderBillingServiceTests
|
||||
|
||||
var tokenizedPaymentMethod = new TokenizedPaymentMethod { Type = TokenizablePaymentMethodType.BankAccount, Token = "token" };
|
||||
|
||||
stripeAdapter.SetupIntentList(Arg.Is<SetupIntentListOptions>(options =>
|
||||
stripeAdapter.ListSetupIntentsAsync(Arg.Is<SetupIntentListOptions>(options =>
|
||||
options.PaymentMethod == tokenizedPaymentMethod.Token)).Returns([
|
||||
new SetupIntent { Id = "setup_intent_id" }
|
||||
]);
|
||||
|
||||
stripeAdapter.CustomerCreateAsync(Arg.Is<CustomerCreateOptions>(o =>
|
||||
stripeAdapter.CreateCustomerAsync(Arg.Is<CustomerCreateOptions>(o =>
|
||||
o.Address.Country == billingAddress.Country &&
|
||||
o.Address.PostalCode == billingAddress.PostalCode &&
|
||||
o.Address.Line1 == billingAddress.Line1 &&
|
||||
@@ -1058,7 +1057,7 @@ public class ProviderBillingServiceTests
|
||||
sutProvider.GetDependency<ISubscriberService>().CreateBraintreeCustomer(provider, tokenizedPaymentMethod.Token)
|
||||
.Returns("braintree_customer_id");
|
||||
|
||||
stripeAdapter.CustomerCreateAsync(Arg.Is<CustomerCreateOptions>(o =>
|
||||
stripeAdapter.CreateCustomerAsync(Arg.Is<CustomerCreateOptions>(o =>
|
||||
o.Address.Country == billingAddress.Country &&
|
||||
o.Address.PostalCode == billingAddress.PostalCode &&
|
||||
o.Address.Line1 == billingAddress.Line1 &&
|
||||
@@ -1100,7 +1099,7 @@ public class ProviderBillingServiceTests
|
||||
|
||||
var tokenizedPaymentMethod = new TokenizedPaymentMethod { Type = TokenizablePaymentMethodType.Card, Token = "token" };
|
||||
|
||||
stripeAdapter.CustomerCreateAsync(Arg.Is<CustomerCreateOptions>(o =>
|
||||
stripeAdapter.CreateCustomerAsync(Arg.Is<CustomerCreateOptions>(o =>
|
||||
o.Address.Country == billingAddress.Country &&
|
||||
o.Address.PostalCode == billingAddress.PostalCode &&
|
||||
o.Address.Line1 == billingAddress.Line1 &&
|
||||
@@ -1142,7 +1141,7 @@ public class ProviderBillingServiceTests
|
||||
|
||||
var tokenizedPaymentMethod = new TokenizedPaymentMethod { Type = TokenizablePaymentMethodType.Card, Token = "token" };
|
||||
|
||||
stripeAdapter.CustomerCreateAsync(Arg.Is<CustomerCreateOptions>(o =>
|
||||
stripeAdapter.CreateCustomerAsync(Arg.Is<CustomerCreateOptions>(o =>
|
||||
o.Address.Country == billingAddress.Country &&
|
||||
o.Address.PostalCode == billingAddress.PostalCode &&
|
||||
o.Address.Line1 == billingAddress.Line1 &&
|
||||
@@ -1178,7 +1177,7 @@ public class ProviderBillingServiceTests
|
||||
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
|
||||
var tokenizedPaymentMethod = new TokenizedPaymentMethod { Type = TokenizablePaymentMethodType.Card, Token = "token" };
|
||||
|
||||
stripeAdapter.CustomerCreateAsync(Arg.Any<CustomerCreateOptions>())
|
||||
stripeAdapter.CreateCustomerAsync(Arg.Any<CustomerCreateOptions>())
|
||||
.Throws(new StripeException("Invalid tax ID") { StripeError = new StripeError { Code = "tax_id_invalid" } });
|
||||
|
||||
var actual = await Assert.ThrowsAsync<BadRequestException>(async () =>
|
||||
@@ -1216,7 +1215,7 @@ public class ProviderBillingServiceTests
|
||||
|
||||
await sutProvider.GetDependency<IStripeAdapter>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.SubscriptionCreateAsync(Arg.Any<SubscriptionCreateOptions>());
|
||||
.CreateSubscriptionAsync(Arg.Any<SubscriptionCreateOptions>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
@@ -1244,7 +1243,7 @@ public class ProviderBillingServiceTests
|
||||
|
||||
await sutProvider.GetDependency<IStripeAdapter>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.SubscriptionCreateAsync(Arg.Any<SubscriptionCreateOptions>());
|
||||
.CreateSubscriptionAsync(Arg.Any<SubscriptionCreateOptions>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
@@ -1272,7 +1271,7 @@ public class ProviderBillingServiceTests
|
||||
|
||||
await sutProvider.GetDependency<IStripeAdapter>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.SubscriptionCreateAsync(Arg.Any<SubscriptionCreateOptions>());
|
||||
.CreateSubscriptionAsync(Arg.Any<SubscriptionCreateOptions>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
@@ -1323,7 +1322,7 @@ public class ProviderBillingServiceTests
|
||||
sutProvider.GetDependency<IProviderPlanRepository>().GetByProviderId(provider.Id)
|
||||
.Returns(providerPlans);
|
||||
|
||||
sutProvider.GetDependency<IStripeAdapter>().SubscriptionCreateAsync(Arg.Any<SubscriptionCreateOptions>())
|
||||
sutProvider.GetDependency<IStripeAdapter>().CreateSubscriptionAsync(Arg.Any<SubscriptionCreateOptions>())
|
||||
.Returns(
|
||||
new Subscription { Id = "subscription_id", Status = StripeConstants.SubscriptionStatus.Incomplete });
|
||||
|
||||
@@ -1381,7 +1380,7 @@ public class ProviderBillingServiceTests
|
||||
|
||||
var expected = new Subscription { Id = "subscription_id", Status = StripeConstants.SubscriptionStatus.Active };
|
||||
|
||||
sutProvider.GetDependency<IStripeAdapter>().SubscriptionCreateAsync(Arg.Is<SubscriptionCreateOptions>(
|
||||
sutProvider.GetDependency<IStripeAdapter>().CreateSubscriptionAsync(Arg.Is<SubscriptionCreateOptions>(
|
||||
sub =>
|
||||
sub.AutomaticTax.Enabled == true &&
|
||||
sub.CollectionMethod == StripeConstants.CollectionMethod.SendInvoice &&
|
||||
@@ -1458,7 +1457,7 @@ public class ProviderBillingServiceTests
|
||||
var expected = new Subscription { Id = "subscription_id", Status = StripeConstants.SubscriptionStatus.Active };
|
||||
|
||||
|
||||
sutProvider.GetDependency<IStripeAdapter>().SubscriptionCreateAsync(Arg.Is<SubscriptionCreateOptions>(
|
||||
sutProvider.GetDependency<IStripeAdapter>().CreateSubscriptionAsync(Arg.Is<SubscriptionCreateOptions>(
|
||||
sub =>
|
||||
sub.AutomaticTax.Enabled == true &&
|
||||
sub.CollectionMethod == StripeConstants.CollectionMethod.ChargeAutomatically &&
|
||||
@@ -1538,7 +1537,7 @@ public class ProviderBillingServiceTests
|
||||
|
||||
sutProvider.GetDependency<ISetupIntentCache>().GetSetupIntentIdForSubscriber(provider.Id).Returns(setupIntentId);
|
||||
|
||||
sutProvider.GetDependency<IStripeAdapter>().SetupIntentGet(setupIntentId, Arg.Is<SetupIntentGetOptions>(options =>
|
||||
sutProvider.GetDependency<IStripeAdapter>().GetSetupIntentAsync(setupIntentId, Arg.Is<SetupIntentGetOptions>(options =>
|
||||
options.Expand.Contains("payment_method"))).Returns(new SetupIntent
|
||||
{
|
||||
Id = setupIntentId,
|
||||
@@ -1553,7 +1552,7 @@ public class ProviderBillingServiceTests
|
||||
}
|
||||
});
|
||||
|
||||
sutProvider.GetDependency<IStripeAdapter>().SubscriptionCreateAsync(Arg.Is<SubscriptionCreateOptions>(
|
||||
sutProvider.GetDependency<IStripeAdapter>().CreateSubscriptionAsync(Arg.Is<SubscriptionCreateOptions>(
|
||||
sub =>
|
||||
sub.AutomaticTax.Enabled == true &&
|
||||
sub.CollectionMethod == StripeConstants.CollectionMethod.ChargeAutomatically &&
|
||||
@@ -1635,7 +1634,7 @@ public class ProviderBillingServiceTests
|
||||
var expected = new Subscription { Id = "subscription_id", Status = StripeConstants.SubscriptionStatus.Active };
|
||||
|
||||
|
||||
sutProvider.GetDependency<IStripeAdapter>().SubscriptionCreateAsync(Arg.Is<SubscriptionCreateOptions>(
|
||||
sutProvider.GetDependency<IStripeAdapter>().CreateSubscriptionAsync(Arg.Is<SubscriptionCreateOptions>(
|
||||
sub =>
|
||||
sub.AutomaticTax.Enabled == true &&
|
||||
sub.CollectionMethod == StripeConstants.CollectionMethod.ChargeAutomatically &&
|
||||
@@ -1713,7 +1712,7 @@ public class ProviderBillingServiceTests
|
||||
var expected = new Subscription { Id = "subscription_id", Status = StripeConstants.SubscriptionStatus.Active };
|
||||
|
||||
|
||||
sutProvider.GetDependency<IStripeAdapter>().SubscriptionCreateAsync(Arg.Is<SubscriptionCreateOptions>(
|
||||
sutProvider.GetDependency<IStripeAdapter>().CreateSubscriptionAsync(Arg.Is<SubscriptionCreateOptions>(
|
||||
sub =>
|
||||
sub.AutomaticTax.Enabled == true &&
|
||||
sub.CollectionMethod == StripeConstants.CollectionMethod.ChargeAutomatically &&
|
||||
@@ -1828,7 +1827,7 @@ public class ProviderBillingServiceTests
|
||||
await providerPlanRepository.Received(1).ReplaceAsync(Arg.Is<ProviderPlan>(
|
||||
providerPlan => providerPlan.PlanType == PlanType.TeamsMonthly && providerPlan.SeatMinimum == 20 && providerPlan.PurchasedSeats == 5));
|
||||
|
||||
await stripeAdapter.Received(1).SubscriptionUpdateAsync(provider.GatewaySubscriptionId,
|
||||
await stripeAdapter.Received(1).UpdateSubscriptionAsync(provider.GatewaySubscriptionId,
|
||||
Arg.Is<SubscriptionUpdateOptions>(
|
||||
options =>
|
||||
options.Items.Count == 2 &&
|
||||
@@ -1908,7 +1907,7 @@ public class ProviderBillingServiceTests
|
||||
await providerPlanRepository.Received(1).ReplaceAsync(Arg.Is<ProviderPlan>(
|
||||
providerPlan => providerPlan.PlanType == PlanType.TeamsMonthly && providerPlan.SeatMinimum == 50));
|
||||
|
||||
await stripeAdapter.Received(1).SubscriptionUpdateAsync(provider.GatewaySubscriptionId,
|
||||
await stripeAdapter.Received(1).UpdateSubscriptionAsync(provider.GatewaySubscriptionId,
|
||||
Arg.Is<SubscriptionUpdateOptions>(
|
||||
options =>
|
||||
options.Items.Count == 2 &&
|
||||
@@ -1989,7 +1988,7 @@ public class ProviderBillingServiceTests
|
||||
providerPlan => providerPlan.PlanType == PlanType.TeamsMonthly && providerPlan.SeatMinimum == 60 && providerPlan.PurchasedSeats == 10));
|
||||
|
||||
await stripeAdapter.DidNotReceiveWithAnyArgs()
|
||||
.SubscriptionUpdateAsync(Arg.Any<string>(), Arg.Any<SubscriptionUpdateOptions>());
|
||||
.UpdateSubscriptionAsync(Arg.Any<string>(), Arg.Any<SubscriptionUpdateOptions>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
@@ -2062,7 +2061,7 @@ public class ProviderBillingServiceTests
|
||||
await providerPlanRepository.Received(1).ReplaceAsync(Arg.Is<ProviderPlan>(
|
||||
providerPlan => providerPlan.PlanType == PlanType.TeamsMonthly && providerPlan.SeatMinimum == 80 && providerPlan.PurchasedSeats == 0));
|
||||
|
||||
await stripeAdapter.Received(1).SubscriptionUpdateAsync(provider.GatewaySubscriptionId,
|
||||
await stripeAdapter.Received(1).UpdateSubscriptionAsync(provider.GatewaySubscriptionId,
|
||||
Arg.Is<SubscriptionUpdateOptions>(
|
||||
options =>
|
||||
options.Items.Count == 2 &&
|
||||
@@ -2142,7 +2141,7 @@ public class ProviderBillingServiceTests
|
||||
await providerPlanRepository.DidNotReceive().ReplaceAsync(Arg.Is<ProviderPlan>(
|
||||
providerPlan => providerPlan.PlanType == PlanType.TeamsMonthly));
|
||||
|
||||
await stripeAdapter.Received(1).SubscriptionUpdateAsync(provider.GatewaySubscriptionId,
|
||||
await stripeAdapter.Received(1).UpdateSubscriptionAsync(provider.GatewaySubscriptionId,
|
||||
Arg.Is<SubscriptionUpdateOptions>(
|
||||
options =>
|
||||
options.Items.Count == 1 &&
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Security.Claims;
|
||||
using Bit.Core;
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.Auth.Entities;
|
||||
using Bit.Core.Auth.Models.Business.Tokenables;
|
||||
using Bit.Core.Auth.Models.Data;
|
||||
using Bit.Core.Auth.Repositories;
|
||||
using Bit.Core.Auth.UserFeatures.Registration;
|
||||
@@ -10,6 +11,7 @@ using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Tokens;
|
||||
using Bit.Sso.Controllers;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
@@ -1137,4 +1139,129 @@ public class AccountControllerTest
|
||||
Assert.NotNull(result.user);
|
||||
Assert.Equal(email, result.user.Email);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void ExternalChallenge_WithMatchingOrgId_Succeeds(
|
||||
SutProvider<AccountController> sutProvider,
|
||||
Organization organization)
|
||||
{
|
||||
// Arrange
|
||||
var orgId = organization.Id;
|
||||
var scheme = orgId.ToString();
|
||||
var returnUrl = "~/vault";
|
||||
var state = "test-state";
|
||||
var userIdentifier = "user-123";
|
||||
var ssoToken = "valid-sso-token";
|
||||
|
||||
// Mock the data protector to return a tokenable with matching org ID
|
||||
var dataProtector = sutProvider.GetDependency<IDataProtectorTokenFactory<SsoTokenable>>();
|
||||
var tokenable = new SsoTokenable(organization, 3600);
|
||||
dataProtector.Unprotect(ssoToken).Returns(tokenable);
|
||||
|
||||
// Mock URL helper for IsLocalUrl check
|
||||
var urlHelper = Substitute.For<IUrlHelper>();
|
||||
urlHelper.IsLocalUrl(returnUrl).Returns(true);
|
||||
sutProvider.Sut.Url = urlHelper;
|
||||
|
||||
// Mock interaction service for IsValidReturnUrl check
|
||||
var interactionService = sutProvider.GetDependency<IIdentityServerInteractionService>();
|
||||
interactionService.IsValidReturnUrl(returnUrl).Returns(true);
|
||||
|
||||
// Act
|
||||
var result = sutProvider.Sut.ExternalChallenge(scheme, returnUrl, state, userIdentifier, ssoToken);
|
||||
|
||||
// Assert
|
||||
var challengeResult = Assert.IsType<ChallengeResult>(result);
|
||||
Assert.Contains(scheme, challengeResult.AuthenticationSchemes);
|
||||
Assert.NotNull(challengeResult.Properties);
|
||||
Assert.Equal(scheme, challengeResult.Properties.Items["scheme"]);
|
||||
Assert.Equal(returnUrl, challengeResult.Properties.Items["return_url"]);
|
||||
Assert.Equal(state, challengeResult.Properties.Items["state"]);
|
||||
Assert.Equal(userIdentifier, challengeResult.Properties.Items["user_identifier"]);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void ExternalChallenge_WithMismatchedOrgId_ThrowsSsoOrganizationIdMismatch(
|
||||
SutProvider<AccountController> sutProvider,
|
||||
Organization organization)
|
||||
{
|
||||
// Arrange
|
||||
var correctOrgId = organization.Id;
|
||||
var wrongOrgId = Guid.NewGuid();
|
||||
var scheme = wrongOrgId.ToString(); // Different from tokenable's org ID
|
||||
var returnUrl = "~/vault";
|
||||
var state = "test-state";
|
||||
var userIdentifier = "user-123";
|
||||
var ssoToken = "valid-sso-token";
|
||||
|
||||
// Mock the data protector to return a tokenable with different org ID
|
||||
var dataProtector = sutProvider.GetDependency<IDataProtectorTokenFactory<SsoTokenable>>();
|
||||
var tokenable = new SsoTokenable(organization, 3600); // Contains correctOrgId
|
||||
dataProtector.Unprotect(ssoToken).Returns(tokenable);
|
||||
|
||||
// Mock i18n service to return the key
|
||||
sutProvider.GetDependency<II18nService>()
|
||||
.T(Arg.Any<string>())
|
||||
.Returns(ci => (string)ci[0]!);
|
||||
|
||||
// Act & Assert
|
||||
var ex = Assert.Throws<Exception>(() =>
|
||||
sutProvider.Sut.ExternalChallenge(scheme, returnUrl, state, userIdentifier, ssoToken));
|
||||
Assert.Equal("SsoOrganizationIdMismatch", ex.Message);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void ExternalChallenge_WithInvalidSchemeFormat_ThrowsSsoOrganizationIdMismatch(
|
||||
SutProvider<AccountController> sutProvider,
|
||||
Organization organization)
|
||||
{
|
||||
// Arrange
|
||||
var scheme = "not-a-valid-guid";
|
||||
var returnUrl = "~/vault";
|
||||
var state = "test-state";
|
||||
var userIdentifier = "user-123";
|
||||
var ssoToken = "valid-sso-token";
|
||||
|
||||
// Mock the data protector to return a valid tokenable
|
||||
var dataProtector = sutProvider.GetDependency<IDataProtectorTokenFactory<SsoTokenable>>();
|
||||
var tokenable = new SsoTokenable(organization, 3600);
|
||||
dataProtector.Unprotect(ssoToken).Returns(tokenable);
|
||||
|
||||
// Mock i18n service to return the key
|
||||
sutProvider.GetDependency<II18nService>()
|
||||
.T(Arg.Any<string>())
|
||||
.Returns(ci => (string)ci[0]!);
|
||||
|
||||
// Act & Assert
|
||||
var ex = Assert.Throws<Exception>(() =>
|
||||
sutProvider.Sut.ExternalChallenge(scheme, returnUrl, state, userIdentifier, ssoToken));
|
||||
Assert.Equal("SsoOrganizationIdMismatch", ex.Message);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void ExternalChallenge_WithInvalidSsoToken_ThrowsInvalidSsoToken(
|
||||
SutProvider<AccountController> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
var orgId = Guid.NewGuid();
|
||||
var scheme = orgId.ToString();
|
||||
var returnUrl = "~/vault";
|
||||
var state = "test-state";
|
||||
var userIdentifier = "user-123";
|
||||
var ssoToken = "invalid-corrupted-token";
|
||||
|
||||
// Mock the data protector to throw when trying to unprotect
|
||||
var dataProtector = sutProvider.GetDependency<IDataProtectorTokenFactory<SsoTokenable>>();
|
||||
dataProtector.Unprotect(ssoToken).Returns(_ => throw new Exception("Token validation failed"));
|
||||
|
||||
// Mock i18n service to return the key
|
||||
sutProvider.GetDependency<II18nService>()
|
||||
.T(Arg.Any<string>())
|
||||
.Returns(ci => (string)ci[0]!);
|
||||
|
||||
// Act & Assert
|
||||
var ex = Assert.Throws<Exception>(() =>
|
||||
sutProvider.Sut.ExternalChallenge(scheme, returnUrl, state, userIdentifier, ssoToken));
|
||||
Assert.Equal("InvalidSsoToken", ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.Billing.Services;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Models.Business;
|
||||
@@ -36,7 +37,7 @@ public class PostUserCommandTests
|
||||
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organizationId).Returns(organization);
|
||||
|
||||
sutProvider.GetDependency<IPaymentService>().HasSecretsManagerStandalone(organization).Returns(true);
|
||||
sutProvider.GetDependency<IStripePaymentService>().HasSecretsManagerStandalone(organization).Returns(true);
|
||||
|
||||
sutProvider.GetDependency<IOrganizationService>()
|
||||
.InviteUserAsync(organizationId,
|
||||
|
||||
@@ -33,6 +33,10 @@
|
||||
"id": "<your Installation Id>",
|
||||
"key": "<your Installation Key>"
|
||||
},
|
||||
"events": {
|
||||
"connectionString": "",
|
||||
"queueName": "event"
|
||||
},
|
||||
"licenseDirectory": "<full path to license directory>",
|
||||
"enableNewDeviceVerification": true,
|
||||
"enableEmailVerification": true
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
},
|
||||
"msbuild-sdks": {
|
||||
"Microsoft.Build.Traversal": "4.1.0",
|
||||
"Microsoft.Build.Sql": "1.0.0"
|
||||
"Microsoft.Build.Sql": "1.0.0",
|
||||
"Bitwarden.Server.Sdk": "1.2.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ using Bit.Core.Billing.Enums;
|
||||
using Bit.Core.Billing.Extensions;
|
||||
using Bit.Core.Billing.Pricing;
|
||||
using Bit.Core.Billing.Providers.Services;
|
||||
using Bit.Core.Billing.Services;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.OrganizationConnectionConfigs;
|
||||
using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Interfaces;
|
||||
@@ -41,7 +42,7 @@ public class OrganizationsController : Controller
|
||||
private readonly ICollectionRepository _collectionRepository;
|
||||
private readonly IGroupRepository _groupRepository;
|
||||
private readonly IPolicyRepository _policyRepository;
|
||||
private readonly IPaymentService _paymentService;
|
||||
private readonly IStripePaymentService _paymentService;
|
||||
private readonly IApplicationCacheService _applicationCacheService;
|
||||
private readonly GlobalSettings _globalSettings;
|
||||
private readonly IProviderRepository _providerRepository;
|
||||
@@ -66,7 +67,7 @@ public class OrganizationsController : Controller
|
||||
ICollectionRepository collectionRepository,
|
||||
IGroupRepository groupRepository,
|
||||
IPolicyRepository policyRepository,
|
||||
IPaymentService paymentService,
|
||||
IStripePaymentService paymentService,
|
||||
IApplicationCacheService applicationCacheService,
|
||||
GlobalSettings globalSettings,
|
||||
IProviderRepository providerRepository,
|
||||
|
||||
@@ -339,11 +339,11 @@ public class ProvidersController : Controller
|
||||
]);
|
||||
await _providerBillingService.UpdateSeatMinimums(updateMspSeatMinimumsCommand);
|
||||
|
||||
var customer = await _stripeAdapter.CustomerGetAsync(provider.GatewayCustomerId);
|
||||
var customer = await _stripeAdapter.GetCustomerAsync(provider.GatewayCustomerId);
|
||||
if (model.PayByInvoice != customer.ApprovedToPayByInvoice())
|
||||
{
|
||||
var approvedToPayByInvoice = model.PayByInvoice ? "1" : "0";
|
||||
await _stripeAdapter.CustomerUpdateAsync(customer.Id, new CustomerUpdateOptions
|
||||
await _stripeAdapter.UpdateCustomerAsync(customer.Id, new CustomerUpdateOptions
|
||||
{
|
||||
Metadata = new Dictionary<string, string>
|
||||
{
|
||||
|
||||
@@ -8,6 +8,7 @@ using Bit.Admin.Utilities;
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.Repositories;
|
||||
using Bit.Core.Billing.Organizations.Queries;
|
||||
using Bit.Core.Billing.Services;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Platform.Installations;
|
||||
using Bit.Core.Repositories;
|
||||
|
||||
@@ -5,6 +5,7 @@ using Bit.Admin.Models;
|
||||
using Bit.Admin.Services;
|
||||
using Bit.Admin.Utilities;
|
||||
using Bit.Core.Auth.UserFeatures.TwoFactorAuth.Interfaces;
|
||||
using Bit.Core.Billing.Services;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Settings;
|
||||
@@ -20,7 +21,7 @@ public class UsersController : Controller
|
||||
{
|
||||
private readonly IUserRepository _userRepository;
|
||||
private readonly ICipherRepository _cipherRepository;
|
||||
private readonly IPaymentService _paymentService;
|
||||
private readonly IStripePaymentService _paymentService;
|
||||
private readonly GlobalSettings _globalSettings;
|
||||
private readonly IAccessControlService _accessControlService;
|
||||
private readonly ITwoFactorIsEnabledQuery _twoFactorIsEnabledQuery;
|
||||
@@ -30,7 +31,7 @@ public class UsersController : Controller
|
||||
public UsersController(
|
||||
IUserRepository userRepository,
|
||||
ICipherRepository cipherRepository,
|
||||
IPaymentService paymentService,
|
||||
IStripePaymentService paymentService,
|
||||
GlobalSettings globalSettings,
|
||||
IAccessControlService accessControlService,
|
||||
ITwoFactorIsEnabledQuery twoFactorIsEnabledQuery,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
using Bit.Api.AdminConsole.Models.Request.Organizations;
|
||||
using Bit.Api.AdminConsole.Models.Response.Organizations;
|
||||
using Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations.Interfaces;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Repositories;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
@@ -12,8 +12,10 @@ namespace Bit.Api.AdminConsole.Controllers;
|
||||
[Authorize("Application")]
|
||||
public class OrganizationIntegrationConfigurationController(
|
||||
ICurrentContext currentContext,
|
||||
IOrganizationIntegrationRepository integrationRepository,
|
||||
IOrganizationIntegrationConfigurationRepository integrationConfigurationRepository) : Controller
|
||||
ICreateOrganizationIntegrationConfigurationCommand createCommand,
|
||||
IUpdateOrganizationIntegrationConfigurationCommand updateCommand,
|
||||
IDeleteOrganizationIntegrationConfigurationCommand deleteCommand,
|
||||
IGetOrganizationIntegrationConfigurationsQuery getQuery) : Controller
|
||||
{
|
||||
[HttpGet("")]
|
||||
public async Task<List<OrganizationIntegrationConfigurationResponseModel>> GetAsync(
|
||||
@@ -24,13 +26,8 @@ public class OrganizationIntegrationConfigurationController(
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
var integration = await integrationRepository.GetByIdAsync(integrationId);
|
||||
if (integration == null || integration.OrganizationId != organizationId)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
var configurations = await integrationConfigurationRepository.GetManyByIntegrationAsync(integrationId);
|
||||
var configurations = await getQuery.GetManyByIntegrationAsync(organizationId, integrationId);
|
||||
return configurations
|
||||
.Select(configuration => new OrganizationIntegrationConfigurationResponseModel(configuration))
|
||||
.ToList();
|
||||
@@ -46,19 +43,11 @@ public class OrganizationIntegrationConfigurationController(
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
var integration = await integrationRepository.GetByIdAsync(integrationId);
|
||||
if (integration == null || integration.OrganizationId != organizationId)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
if (!model.IsValidForType(integration.Type))
|
||||
{
|
||||
throw new BadRequestException($"Invalid Configuration and/or Template for integration type {integration.Type}");
|
||||
}
|
||||
|
||||
var organizationIntegrationConfiguration = model.ToOrganizationIntegrationConfiguration(integrationId);
|
||||
var configuration = await integrationConfigurationRepository.CreateAsync(organizationIntegrationConfiguration);
|
||||
return new OrganizationIntegrationConfigurationResponseModel(configuration);
|
||||
var configuration = model.ToOrganizationIntegrationConfiguration(integrationId);
|
||||
var created = await createCommand.CreateAsync(organizationId, integrationId, configuration);
|
||||
|
||||
return new OrganizationIntegrationConfigurationResponseModel(created);
|
||||
}
|
||||
|
||||
[HttpPut("{configurationId:guid}")]
|
||||
@@ -72,26 +61,11 @@ public class OrganizationIntegrationConfigurationController(
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
var integration = await integrationRepository.GetByIdAsync(integrationId);
|
||||
if (integration == null || integration.OrganizationId != organizationId)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
if (!model.IsValidForType(integration.Type))
|
||||
{
|
||||
throw new BadRequestException($"Invalid Configuration and/or Template for integration type {integration.Type}");
|
||||
}
|
||||
|
||||
var configuration = await integrationConfigurationRepository.GetByIdAsync(configurationId);
|
||||
if (configuration is null || configuration.OrganizationIntegrationId != integrationId)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
var configuration = model.ToOrganizationIntegrationConfiguration(integrationId);
|
||||
var updated = await updateCommand.UpdateAsync(organizationId, integrationId, configurationId, configuration);
|
||||
|
||||
var newConfiguration = model.ToOrganizationIntegrationConfiguration(configuration);
|
||||
await integrationConfigurationRepository.ReplaceAsync(newConfiguration);
|
||||
|
||||
return new OrganizationIntegrationConfigurationResponseModel(newConfiguration);
|
||||
return new OrganizationIntegrationConfigurationResponseModel(updated);
|
||||
}
|
||||
|
||||
[HttpDelete("{configurationId:guid}")]
|
||||
@@ -101,19 +75,8 @@ public class OrganizationIntegrationConfigurationController(
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
var integration = await integrationRepository.GetByIdAsync(integrationId);
|
||||
if (integration == null || integration.OrganizationId != organizationId)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
var configuration = await integrationConfigurationRepository.GetByIdAsync(configurationId);
|
||||
if (configuration is null || configuration.OrganizationIntegrationId != integrationId)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
await integrationConfigurationRepository.DeleteAsync(configuration);
|
||||
await deleteCommand.DeleteAsync(organizationId, integrationId, configurationId);
|
||||
}
|
||||
|
||||
[HttpPost("{configurationId:guid}/delete")]
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using System.Text.Json;
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.Models.Data.EventIntegrations;
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.Enums;
|
||||
|
||||
|
||||
@@ -16,38 +14,6 @@ public class OrganizationIntegrationConfigurationRequestModel
|
||||
|
||||
public string? Template { get; set; }
|
||||
|
||||
public bool IsValidForType(IntegrationType integrationType)
|
||||
{
|
||||
switch (integrationType)
|
||||
{
|
||||
case IntegrationType.CloudBillingSync or IntegrationType.Scim:
|
||||
return false;
|
||||
case IntegrationType.Slack:
|
||||
return !string.IsNullOrWhiteSpace(Template) &&
|
||||
IsConfigurationValid<SlackIntegrationConfiguration>() &&
|
||||
IsFiltersValid();
|
||||
case IntegrationType.Webhook:
|
||||
return !string.IsNullOrWhiteSpace(Template) &&
|
||||
IsConfigurationValid<WebhookIntegrationConfiguration>() &&
|
||||
IsFiltersValid();
|
||||
case IntegrationType.Hec:
|
||||
return !string.IsNullOrWhiteSpace(Template) &&
|
||||
Configuration is null &&
|
||||
IsFiltersValid();
|
||||
case IntegrationType.Datadog:
|
||||
return !string.IsNullOrWhiteSpace(Template) &&
|
||||
Configuration is null &&
|
||||
IsFiltersValid();
|
||||
case IntegrationType.Teams:
|
||||
return !string.IsNullOrWhiteSpace(Template) &&
|
||||
Configuration is null &&
|
||||
IsFiltersValid();
|
||||
default:
|
||||
return false;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public OrganizationIntegrationConfiguration ToOrganizationIntegrationConfiguration(Guid organizationIntegrationId)
|
||||
{
|
||||
return new OrganizationIntegrationConfiguration()
|
||||
@@ -59,50 +25,4 @@ public class OrganizationIntegrationConfigurationRequestModel
|
||||
Template = Template
|
||||
};
|
||||
}
|
||||
|
||||
public OrganizationIntegrationConfiguration ToOrganizationIntegrationConfiguration(OrganizationIntegrationConfiguration currentConfiguration)
|
||||
{
|
||||
currentConfiguration.Configuration = Configuration;
|
||||
currentConfiguration.EventType = EventType;
|
||||
currentConfiguration.Filters = Filters;
|
||||
currentConfiguration.Template = Template;
|
||||
|
||||
return currentConfiguration;
|
||||
}
|
||||
|
||||
private bool IsConfigurationValid<T>()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(Configuration))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var config = JsonSerializer.Deserialize<T>(Configuration);
|
||||
return config is not null;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsFiltersValid()
|
||||
{
|
||||
if (Filters is null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var filters = JsonSerializer.Deserialize<IntegrationFilterGroup>(Filters);
|
||||
return filters is not null;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers;
|
||||
using Bit.Core.AdminConsole.Repositories;
|
||||
using Bit.Core.Auth.UserFeatures.TwoFactorAuth.Interfaces;
|
||||
using Bit.Core.Billing.Services;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
@@ -24,7 +25,7 @@ public class MembersController : Controller
|
||||
private readonly ICurrentContext _currentContext;
|
||||
private readonly IUpdateOrganizationUserCommand _updateOrganizationUserCommand;
|
||||
private readonly IUpdateOrganizationUserGroupsCommand _updateOrganizationUserGroupsCommand;
|
||||
private readonly IPaymentService _paymentService;
|
||||
private readonly IStripePaymentService _paymentService;
|
||||
private readonly IOrganizationRepository _organizationRepository;
|
||||
private readonly ITwoFactorIsEnabledQuery _twoFactorIsEnabledQuery;
|
||||
private readonly IRemoveOrganizationUserCommand _removeOrganizationUserCommand;
|
||||
@@ -37,7 +38,7 @@ public class MembersController : Controller
|
||||
ICurrentContext currentContext,
|
||||
IUpdateOrganizationUserCommand updateOrganizationUserCommand,
|
||||
IUpdateOrganizationUserGroupsCommand updateOrganizationUserGroupsCommand,
|
||||
IPaymentService paymentService,
|
||||
IStripePaymentService paymentService,
|
||||
IOrganizationRepository organizationRepository,
|
||||
ITwoFactorIsEnabledQuery twoFactorIsEnabledQuery,
|
||||
IRemoveOrganizationUserCommand removeOrganizationUserCommand,
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace Bit.Api.Billing.Controllers;
|
||||
[Route("accounts/billing")]
|
||||
[Authorize("Application")]
|
||||
public class AccountsBillingController(
|
||||
IPaymentService paymentService,
|
||||
IStripePaymentService paymentService,
|
||||
IUserService userService,
|
||||
IPaymentHistoryService paymentHistoryService) : Controller
|
||||
{
|
||||
|
||||
@@ -79,7 +79,7 @@ public class AccountsController(
|
||||
[HttpGet("subscription")]
|
||||
public async Task<SubscriptionResponseModel> GetSubscriptionAsync(
|
||||
[FromServices] GlobalSettings globalSettings,
|
||||
[FromServices] IPaymentService paymentService)
|
||||
[FromServices] IStripePaymentService paymentService)
|
||||
{
|
||||
var user = await userService.GetUserByPrincipalAsync(User);
|
||||
if (user == null)
|
||||
|
||||
@@ -5,7 +5,6 @@ using Bit.Core.Billing.Providers.Services;
|
||||
using Bit.Core.Billing.Services;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Utilities;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
@@ -19,7 +18,7 @@ public class OrganizationBillingController(
|
||||
ICurrentContext currentContext,
|
||||
IOrganizationBillingService organizationBillingService,
|
||||
IOrganizationRepository organizationRepository,
|
||||
IPaymentService paymentService,
|
||||
IStripePaymentService paymentService,
|
||||
IPaymentHistoryService paymentHistoryService) : BaseBillingController
|
||||
{
|
||||
// TODO: Remove when pm-25379-use-new-organization-metadata-structure is removed.
|
||||
|
||||
@@ -36,7 +36,7 @@ public class OrganizationsController(
|
||||
IOrganizationUserRepository organizationUserRepository,
|
||||
IOrganizationService organizationService,
|
||||
IUserService userService,
|
||||
IPaymentService paymentService,
|
||||
IStripePaymentService paymentService,
|
||||
ICurrentContext currentContext,
|
||||
IGetCloudOrganizationLicenseQuery getCloudOrganizationLicenseQuery,
|
||||
GlobalSettings globalSettings,
|
||||
|
||||
@@ -43,7 +43,7 @@ public class ProviderBillingController(
|
||||
return result;
|
||||
}
|
||||
|
||||
var invoices = await stripeAdapter.InvoiceListAsync(new StripeInvoiceListOptions
|
||||
var invoices = await stripeAdapter.ListInvoicesAsync(new StripeInvoiceListOptions
|
||||
{
|
||||
Customer = provider.GatewayCustomerId
|
||||
});
|
||||
@@ -87,7 +87,7 @@ public class ProviderBillingController(
|
||||
return result;
|
||||
}
|
||||
|
||||
var subscription = await stripeAdapter.SubscriptionGetAsync(provider.GatewaySubscriptionId,
|
||||
var subscription = await stripeAdapter.GetSubscriptionAsync(provider.GatewaySubscriptionId,
|
||||
new SubscriptionGetOptions { Expand = ["customer.tax_ids", "discounts", "test_clock"] });
|
||||
|
||||
var providerPlans = await providerPlanRepository.GetByProviderId(provider.Id);
|
||||
@@ -96,7 +96,7 @@ public class ProviderBillingController(
|
||||
{
|
||||
var plan = await pricingClient.GetPlanOrThrow(providerPlan.PlanType);
|
||||
var priceId = ProviderPriceAdapter.GetPriceId(provider, subscription, plan.Type);
|
||||
var price = await stripeAdapter.PriceGetAsync(priceId);
|
||||
var price = await stripeAdapter.GetPriceAsync(priceId);
|
||||
|
||||
var unitAmount = price.UnitAmountDecimal.HasValue
|
||||
? price.UnitAmountDecimal.Value / 100M
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using Bit.Core.Billing.Tax.Services;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Billing.Services;
|
||||
using Bit.Core.Billing.Tax.Services;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http.HttpResults;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
@@ -28,7 +28,7 @@ public class StripeController(
|
||||
Usage = "off_session"
|
||||
};
|
||||
|
||||
var setupIntent = await stripeAdapter.SetupIntentCreate(options);
|
||||
var setupIntent = await stripeAdapter.CreateSetupIntentAsync(options);
|
||||
|
||||
return TypedResults.Ok(setupIntent.ClientSecret);
|
||||
}
|
||||
@@ -43,7 +43,7 @@ public class StripeController(
|
||||
Usage = "off_session"
|
||||
};
|
||||
|
||||
var setupIntent = await stripeAdapter.SetupIntentCreate(options);
|
||||
var setupIntent = await stripeAdapter.CreateSetupIntentAsync(options);
|
||||
|
||||
return TypedResults.Ok(setupIntent.ClientSecret);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,17 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
<Sdk Name="Bitwarden.Server.Sdk" />
|
||||
|
||||
<PropertyGroup>
|
||||
<UserSecretsId>bitwarden-Billing</UserSecretsId>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Label="Server SDK settings">
|
||||
<!-- These features will be gradually turned on -->
|
||||
<BitIncludeFeatures>false</BitIncludeFeatures>
|
||||
<BitIncludeTelemetry>false</BitIncludeTelemetry>
|
||||
<BitIncludeAuthentication>false</BitIncludeAuthentication>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(RunConfiguration)' == 'Billing' " />
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\bitwarden_license\src\Commercial.Core\Commercial.Core.csproj" />
|
||||
|
||||
@@ -29,7 +29,7 @@ public class BitPayController(
|
||||
IUserRepository userRepository,
|
||||
IProviderRepository providerRepository,
|
||||
IMailService mailService,
|
||||
IPaymentService paymentService,
|
||||
IStripePaymentService paymentService,
|
||||
ILogger<BitPayController> logger,
|
||||
IPremiumUserBillingService premiumUserBillingService)
|
||||
: Controller
|
||||
|
||||
@@ -23,7 +23,7 @@ public class PayPalController : Controller
|
||||
private readonly ILogger<PayPalController> _logger;
|
||||
private readonly IMailService _mailService;
|
||||
private readonly IOrganizationRepository _organizationRepository;
|
||||
private readonly IPaymentService _paymentService;
|
||||
private readonly IStripePaymentService _paymentService;
|
||||
private readonly ITransactionRepository _transactionRepository;
|
||||
private readonly IUserRepository _userRepository;
|
||||
private readonly IProviderRepository _providerRepository;
|
||||
@@ -34,7 +34,7 @@ public class PayPalController : Controller
|
||||
ILogger<PayPalController> logger,
|
||||
IMailService mailService,
|
||||
IOrganizationRepository organizationRepository,
|
||||
IPaymentService paymentService,
|
||||
IStripePaymentService paymentService,
|
||||
ITransactionRepository transactionRepository,
|
||||
IUserRepository userRepository,
|
||||
IProviderRepository providerRepository,
|
||||
|
||||
@@ -8,6 +8,7 @@ public class Program
|
||||
{
|
||||
Host
|
||||
.CreateDefaultBuilder(args)
|
||||
.UseBitwardenSdk()
|
||||
.ConfigureWebHostDefaults(webBuilder =>
|
||||
{
|
||||
webBuilder.UseStartup<Startup>();
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
using Bit.Core.AdminConsole.Entities.Provider;
|
||||
using Bit.Core.AdminConsole.Repositories;
|
||||
using Bit.Core.Billing.Caches;
|
||||
using Bit.Core.Billing.Services;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using OneOf;
|
||||
using Stripe;
|
||||
using Event = Stripe.Event;
|
||||
@@ -59,10 +59,10 @@ public class SetupIntentSucceededHandler(
|
||||
return;
|
||||
}
|
||||
|
||||
await stripeAdapter.PaymentMethodAttachAsync(paymentMethod.Id,
|
||||
await stripeAdapter.AttachPaymentMethodAsync(paymentMethod.Id,
|
||||
new PaymentMethodAttachOptions { Customer = customerId });
|
||||
|
||||
await stripeAdapter.CustomerUpdateAsync(customerId, new CustomerUpdateOptions
|
||||
await stripeAdapter.UpdateCustomerAsync(customerId, new CustomerUpdateOptions
|
||||
{
|
||||
InvoiceSettings = new CustomerInvoiceSettingsOptions
|
||||
{
|
||||
|
||||
@@ -109,8 +109,7 @@ public class SubscriptionUpdatedHandler : ISubscriptionUpdatedHandler
|
||||
break;
|
||||
}
|
||||
|
||||
if (subscription.Status is StripeSubscriptionStatus.Unpaid &&
|
||||
subscription.Items.Any(i => i.Price.Id is IStripeEventUtilityService.PremiumPlanId or IStripeEventUtilityService.PremiumPlanIdAppStore))
|
||||
if (await IsPremiumSubscriptionAsync(subscription))
|
||||
{
|
||||
await CancelSubscription(subscription.Id);
|
||||
await VoidOpenInvoices(subscription.Id);
|
||||
@@ -118,6 +117,20 @@ public class SubscriptionUpdatedHandler : ISubscriptionUpdatedHandler
|
||||
|
||||
await _userService.DisablePremiumAsync(userId.Value, currentPeriodEnd);
|
||||
|
||||
break;
|
||||
}
|
||||
case StripeSubscriptionStatus.Incomplete when userId.HasValue:
|
||||
{
|
||||
// Handle Incomplete subscriptions for Premium users that have open invoices from failed payments
|
||||
// This prevents duplicate subscriptions when users retry the subscription flow
|
||||
if (await IsPremiumSubscriptionAsync(subscription) &&
|
||||
subscription.LatestInvoice is { Status: StripeInvoiceStatus.Open })
|
||||
{
|
||||
await CancelSubscription(subscription.Id);
|
||||
await VoidOpenInvoices(subscription.Id);
|
||||
await _userService.DisablePremiumAsync(userId.Value, currentPeriodEnd);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case StripeSubscriptionStatus.Active when organizationId.HasValue:
|
||||
@@ -190,6 +203,13 @@ public class SubscriptionUpdatedHandler : ISubscriptionUpdatedHandler
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<bool> IsPremiumSubscriptionAsync(Subscription subscription)
|
||||
{
|
||||
var premiumPlans = await _pricingClient.ListPremiumPlans();
|
||||
var premiumPriceIds = premiumPlans.SelectMany(p => new[] { p.Seat.StripePriceId, p.Storage.StripePriceId }).ToHashSet();
|
||||
return subscription.Items.Any(i => premiumPriceIds.Contains(i.Price.Id));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the provider subscription status has changed from a non-active to an active status type
|
||||
/// If the previous status is already active(active,past-due,trialing),canceled,or null, then this will return false.
|
||||
|
||||
@@ -1,8 +1,24 @@
|
||||
using Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrations;
|
||||
using Azure.Messaging.ServiceBus;
|
||||
using Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations;
|
||||
using Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations.Interfaces;
|
||||
using Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrations;
|
||||
using Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrations.Interfaces;
|
||||
using Bit.Core.AdminConsole.Models.Data.EventIntegrations;
|
||||
using Bit.Core.AdminConsole.Models.Teams;
|
||||
using Bit.Core.AdminConsole.Repositories;
|
||||
using Bit.Core.AdminConsole.Services;
|
||||
using Bit.Core.AdminConsole.Services.NoopImplementations;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Settings;
|
||||
using Bit.Core.Utilities;
|
||||
using Microsoft.Bot.Builder;
|
||||
using Microsoft.Bot.Builder.Integration.AspNet.Core;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using ZiggyCreatures.Caching.Fusion;
|
||||
using TableStorageRepos = Bit.Core.Repositories.TableStorage;
|
||||
|
||||
namespace Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
@@ -20,8 +36,467 @@ public static class EventIntegrationsServiceCollectionExtensions
|
||||
// This is idempotent for the same named cache, so it's safe to call.
|
||||
services.AddExtendedCache(EventIntegrationsCacheConstants.CacheName, globalSettings);
|
||||
|
||||
// Add Validator
|
||||
services.TryAddSingleton<IOrganizationIntegrationConfigurationValidator, OrganizationIntegrationConfigurationValidator>();
|
||||
|
||||
// Add all commands/queries
|
||||
services.AddOrganizationIntegrationCommandsQueries();
|
||||
services.AddOrganizationIntegrationConfigurationCommandsQueries();
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers event write services based on available configuration.
|
||||
/// </summary>
|
||||
/// <param name="services">The service collection to add services to.</param>
|
||||
/// <param name="globalSettings">The global settings containing event logging configuration.</param>
|
||||
/// <returns>The service collection for chaining.</returns>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This method registers the appropriate IEventWriteService implementation based on the available
|
||||
/// configuration, checking in the following priority order:
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// 1. Azure Service Bus - If all Azure Service Bus settings are present, registers
|
||||
/// EventIntegrationEventWriteService with AzureServiceBusService as the publisher
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// 2. RabbitMQ - If all RabbitMQ settings are present, registers EventIntegrationEventWriteService with
|
||||
/// RabbitMqService as the publisher
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// 3. Azure Queue Storage - If Events.ConnectionString is present, registers AzureQueueEventWriteService
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// 4. Repository (Self-Hosted) - If SelfHosted is true, registers RepositoryEventWriteService
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// 5. Noop - If none of the above are configured, registers NoopEventWriteService (no-op implementation)
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public static IServiceCollection AddEventWriteServices(this IServiceCollection services, GlobalSettings globalSettings)
|
||||
{
|
||||
if (IsAzureServiceBusEnabled(globalSettings))
|
||||
{
|
||||
services.TryAddSingleton<IEventIntegrationPublisher, AzureServiceBusService>();
|
||||
services.TryAddSingleton<IEventWriteService, EventIntegrationEventWriteService>();
|
||||
return services;
|
||||
}
|
||||
|
||||
if (IsRabbitMqEnabled(globalSettings))
|
||||
{
|
||||
services.TryAddSingleton<IEventIntegrationPublisher, RabbitMqService>();
|
||||
services.TryAddSingleton<IEventWriteService, EventIntegrationEventWriteService>();
|
||||
return services;
|
||||
}
|
||||
|
||||
if (CoreHelpers.SettingHasValue(globalSettings.Events.ConnectionString) &&
|
||||
CoreHelpers.SettingHasValue(globalSettings.Events.QueueName))
|
||||
{
|
||||
services.TryAddSingleton<IEventWriteService, AzureQueueEventWriteService>();
|
||||
return services;
|
||||
}
|
||||
|
||||
if (globalSettings.SelfHosted)
|
||||
{
|
||||
services.TryAddSingleton<IEventWriteService, RepositoryEventWriteService>();
|
||||
return services;
|
||||
}
|
||||
|
||||
services.TryAddSingleton<IEventWriteService, NoopEventWriteService>();
|
||||
return services;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers Azure Service Bus-based event integration listeners and supporting infrastructure.
|
||||
/// </summary>
|
||||
/// <param name="services">The service collection to add services to.</param>
|
||||
/// <param name="globalSettings">The global settings containing Azure Service Bus configuration.</param>
|
||||
/// <returns>The service collection for chaining.</returns>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// If Azure Service Bus is not enabled (missing required settings), this method returns immediately
|
||||
/// without registering any services.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// When Azure Service Bus is enabled, this method registers:
|
||||
/// - IAzureServiceBusService and IEventIntegrationPublisher implementations
|
||||
/// - Table Storage event repository
|
||||
/// - Azure Table Storage event handler
|
||||
/// - All event integration services via AddEventIntegrationServices
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// PREREQUISITE: Callers must ensure AddDistributedCache has been called before this method,
|
||||
/// as it is required to create the event integrations extended cache.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public static IServiceCollection AddAzureServiceBusListeners(this IServiceCollection services, GlobalSettings globalSettings)
|
||||
{
|
||||
if (!IsAzureServiceBusEnabled(globalSettings))
|
||||
{
|
||||
return services;
|
||||
}
|
||||
|
||||
services.TryAddSingleton<IAzureServiceBusService, AzureServiceBusService>();
|
||||
services.TryAddSingleton<IEventIntegrationPublisher, AzureServiceBusService>();
|
||||
services.TryAddSingleton<IEventRepository, TableStorageRepos.EventRepository>();
|
||||
services.TryAddKeyedSingleton<IEventWriteService, RepositoryEventWriteService>("persistent");
|
||||
services.TryAddSingleton<AzureTableStorageEventHandler>();
|
||||
|
||||
services.AddEventIntegrationServices(globalSettings);
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers RabbitMQ-based event integration listeners and supporting infrastructure.
|
||||
/// </summary>
|
||||
/// <param name="services">The service collection to add services to.</param>
|
||||
/// <param name="globalSettings">The global settings containing RabbitMQ configuration.</param>
|
||||
/// <returns>The service collection for chaining.</returns>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// If RabbitMQ is not enabled (missing required settings), this method returns immediately
|
||||
/// without registering any services.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// When RabbitMQ is enabled, this method registers:
|
||||
/// - IRabbitMqService and IEventIntegrationPublisher implementations
|
||||
/// - Event repository handler
|
||||
/// - All event integration services via AddEventIntegrationServices
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// PREREQUISITE: Callers must ensure AddDistributedCache has been called before this method,
|
||||
/// as it is required to create the event integrations extended cache.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public static IServiceCollection AddRabbitMqListeners(this IServiceCollection services, GlobalSettings globalSettings)
|
||||
{
|
||||
if (!IsRabbitMqEnabled(globalSettings))
|
||||
{
|
||||
return services;
|
||||
}
|
||||
|
||||
services.TryAddSingleton<IRabbitMqService, RabbitMqService>();
|
||||
services.TryAddSingleton<IEventIntegrationPublisher, RabbitMqService>();
|
||||
services.TryAddSingleton<EventRepositoryHandler>();
|
||||
|
||||
services.AddEventIntegrationServices(globalSettings);
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers Slack integration services based on configuration settings.
|
||||
/// </summary>
|
||||
/// <param name="services">The service collection to add services to.</param>
|
||||
/// <param name="globalSettings">The global settings containing Slack configuration.</param>
|
||||
/// <returns>The service collection for chaining.</returns>
|
||||
/// <remarks>
|
||||
/// If all required Slack settings are configured (ClientId, ClientSecret, Scopes), registers the full SlackService,
|
||||
/// including an HttpClient for Slack API calls. Otherwise, registers a NoopSlackService that performs no operations.
|
||||
/// </remarks>
|
||||
public static IServiceCollection AddSlackService(this IServiceCollection services, GlobalSettings globalSettings)
|
||||
{
|
||||
if (CoreHelpers.SettingHasValue(globalSettings.Slack.ClientId) &&
|
||||
CoreHelpers.SettingHasValue(globalSettings.Slack.ClientSecret) &&
|
||||
CoreHelpers.SettingHasValue(globalSettings.Slack.Scopes))
|
||||
{
|
||||
services.AddHttpClient(SlackService.HttpClientName);
|
||||
services.TryAddSingleton<ISlackService, SlackService>();
|
||||
}
|
||||
else
|
||||
{
|
||||
services.TryAddSingleton<ISlackService, NoopSlackService>();
|
||||
}
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers Microsoft Teams integration services based on configuration settings.
|
||||
/// </summary>
|
||||
/// <param name="services">The service collection to add services to.</param>
|
||||
/// <param name="globalSettings">The global settings containing Teams configuration.</param>
|
||||
/// <returns>The service collection for chaining.</returns>
|
||||
/// <remarks>
|
||||
/// If all required Teams settings are configured (ClientId, ClientSecret, Scopes), registers:
|
||||
/// - TeamsService and its interfaces (IBot, ITeamsService)
|
||||
/// - IBotFrameworkHttpAdapter with Teams credentials
|
||||
/// - HttpClient for Teams API calls
|
||||
/// Otherwise, registers a NoopTeamsService that performs no operations.
|
||||
/// </remarks>
|
||||
public static IServiceCollection AddTeamsService(this IServiceCollection services, GlobalSettings globalSettings)
|
||||
{
|
||||
if (CoreHelpers.SettingHasValue(globalSettings.Teams.ClientId) &&
|
||||
CoreHelpers.SettingHasValue(globalSettings.Teams.ClientSecret) &&
|
||||
CoreHelpers.SettingHasValue(globalSettings.Teams.Scopes))
|
||||
{
|
||||
services.AddHttpClient(TeamsService.HttpClientName);
|
||||
services.TryAddSingleton<TeamsService>();
|
||||
services.TryAddSingleton<IBot>(sp => sp.GetRequiredService<TeamsService>());
|
||||
services.TryAddSingleton<ITeamsService>(sp => sp.GetRequiredService<TeamsService>());
|
||||
services.TryAddSingleton<IBotFrameworkHttpAdapter>(_ =>
|
||||
new BotFrameworkHttpAdapter(
|
||||
new TeamsBotCredentialProvider(
|
||||
clientId: globalSettings.Teams.ClientId,
|
||||
clientSecret: globalSettings.Teams.ClientSecret
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
services.TryAddSingleton<ITeamsService, NoopTeamsService>();
|
||||
}
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers event integration services including handlers, listeners, and supporting infrastructure.
|
||||
/// </summary>
|
||||
/// <param name="services">The service collection to add services to.</param>
|
||||
/// <param name="globalSettings">The global settings containing integration configuration.</param>
|
||||
/// <returns>The service collection for chaining.</returns>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This method orchestrates the registration of all event integration components based on the enabled
|
||||
/// message broker (Azure Service Bus or RabbitMQ). It is an internal method called by the public
|
||||
/// entry points AddAzureServiceBusListeners and AddRabbitMqListeners.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// NOTE: If both Azure Service Bus and RabbitMQ are configured, Azure Service Bus takes precedence. This means that
|
||||
/// Azure Service Bus listeners will be registered (and RabbitMQ listeners will NOT) even if this event is called
|
||||
/// from AddRabbitMqListeners when Azure Service Bus settings are configured.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// PREREQUISITE: Callers must ensure AddDistributedCache has been called before invoking this method.
|
||||
/// This method depends on distributed cache infrastructure being available for the keyed extended
|
||||
/// cache registration.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Registered Services:
|
||||
/// - Keyed ExtendedCache for event integrations
|
||||
/// - Integration filter service
|
||||
/// - Integration handlers for Slack, Webhook, Hec, Datadog, and Teams
|
||||
/// - Hosted services for event and integration listeners (based on enabled message broker)
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
internal static IServiceCollection AddEventIntegrationServices(this IServiceCollection services,
|
||||
GlobalSettings globalSettings)
|
||||
{
|
||||
// Add common services
|
||||
// NOTE: AddDistributedCache must be called by the caller before this method
|
||||
services.AddExtendedCache(EventIntegrationsCacheConstants.CacheName, globalSettings);
|
||||
services.TryAddSingleton<IIntegrationFilterService, IntegrationFilterService>();
|
||||
services.TryAddKeyedSingleton<IEventWriteService, RepositoryEventWriteService>("persistent");
|
||||
|
||||
// Add services in support of handlers
|
||||
services.AddSlackService(globalSettings);
|
||||
services.AddTeamsService(globalSettings);
|
||||
services.TryAddSingleton(TimeProvider.System);
|
||||
services.AddHttpClient(WebhookIntegrationHandler.HttpClientName);
|
||||
services.AddHttpClient(DatadogIntegrationHandler.HttpClientName);
|
||||
|
||||
// Add integration handlers
|
||||
services.TryAddSingleton<IIntegrationHandler<SlackIntegrationConfigurationDetails>, SlackIntegrationHandler>();
|
||||
services.TryAddSingleton<IIntegrationHandler<WebhookIntegrationConfigurationDetails>, WebhookIntegrationHandler>();
|
||||
services.TryAddSingleton<IIntegrationHandler<DatadogIntegrationConfigurationDetails>, DatadogIntegrationHandler>();
|
||||
services.TryAddSingleton<IIntegrationHandler<TeamsIntegrationConfigurationDetails>, TeamsIntegrationHandler>();
|
||||
|
||||
var repositoryConfiguration = new RepositoryListenerConfiguration(globalSettings);
|
||||
var slackConfiguration = new SlackListenerConfiguration(globalSettings);
|
||||
var webhookConfiguration = new WebhookListenerConfiguration(globalSettings);
|
||||
var hecConfiguration = new HecListenerConfiguration(globalSettings);
|
||||
var datadogConfiguration = new DatadogListenerConfiguration(globalSettings);
|
||||
var teamsConfiguration = new TeamsListenerConfiguration(globalSettings);
|
||||
|
||||
if (IsAzureServiceBusEnabled(globalSettings))
|
||||
{
|
||||
services.TryAddEnumerable(ServiceDescriptor.Singleton<IHostedService,
|
||||
AzureServiceBusEventListenerService<RepositoryListenerConfiguration>>(provider =>
|
||||
new AzureServiceBusEventListenerService<RepositoryListenerConfiguration>(
|
||||
configuration: repositoryConfiguration,
|
||||
handler: provider.GetRequiredService<AzureTableStorageEventHandler>(),
|
||||
serviceBusService: provider.GetRequiredService<IAzureServiceBusService>(),
|
||||
serviceBusOptions: new ServiceBusProcessorOptions()
|
||||
{
|
||||
PrefetchCount = repositoryConfiguration.EventPrefetchCount,
|
||||
MaxConcurrentCalls = repositoryConfiguration.EventMaxConcurrentCalls
|
||||
},
|
||||
loggerFactory: provider.GetRequiredService<ILoggerFactory>()
|
||||
)
|
||||
)
|
||||
);
|
||||
services.AddAzureServiceBusIntegration<SlackIntegrationConfigurationDetails, SlackListenerConfiguration>(slackConfiguration);
|
||||
services.AddAzureServiceBusIntegration<WebhookIntegrationConfigurationDetails, WebhookListenerConfiguration>(webhookConfiguration);
|
||||
services.AddAzureServiceBusIntegration<WebhookIntegrationConfigurationDetails, HecListenerConfiguration>(hecConfiguration);
|
||||
services.AddAzureServiceBusIntegration<DatadogIntegrationConfigurationDetails, DatadogListenerConfiguration>(datadogConfiguration);
|
||||
services.AddAzureServiceBusIntegration<TeamsIntegrationConfigurationDetails, TeamsListenerConfiguration>(teamsConfiguration);
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
if (IsRabbitMqEnabled(globalSettings))
|
||||
{
|
||||
services.TryAddEnumerable(ServiceDescriptor.Singleton<IHostedService,
|
||||
RabbitMqEventListenerService<RepositoryListenerConfiguration>>(provider =>
|
||||
new RabbitMqEventListenerService<RepositoryListenerConfiguration>(
|
||||
handler: provider.GetRequiredService<EventRepositoryHandler>(),
|
||||
configuration: repositoryConfiguration,
|
||||
rabbitMqService: provider.GetRequiredService<IRabbitMqService>(),
|
||||
loggerFactory: provider.GetRequiredService<ILoggerFactory>()
|
||||
)
|
||||
)
|
||||
);
|
||||
services.AddRabbitMqIntegration<SlackIntegrationConfigurationDetails, SlackListenerConfiguration>(slackConfiguration);
|
||||
services.AddRabbitMqIntegration<WebhookIntegrationConfigurationDetails, WebhookListenerConfiguration>(webhookConfiguration);
|
||||
services.AddRabbitMqIntegration<WebhookIntegrationConfigurationDetails, HecListenerConfiguration>(hecConfiguration);
|
||||
services.AddRabbitMqIntegration<DatadogIntegrationConfigurationDetails, DatadogListenerConfiguration>(datadogConfiguration);
|
||||
services.AddRabbitMqIntegration<TeamsIntegrationConfigurationDetails, TeamsListenerConfiguration>(teamsConfiguration);
|
||||
}
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers Azure Service Bus-based event integration listeners for a specific integration type.
|
||||
/// </summary>
|
||||
/// <typeparam name="TConfig">The integration configuration details type (e.g., SlackIntegrationConfigurationDetails).</typeparam>
|
||||
/// <typeparam name="TListenerConfig">The listener configuration type implementing IIntegrationListenerConfiguration.</typeparam>
|
||||
/// <param name="services">The service collection to add services to.</param>
|
||||
/// <param name="listenerConfiguration">The listener configuration containing routing keys and message processing settings.</param>
|
||||
/// <returns>The service collection for chaining.</returns>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This method registers three key components:
|
||||
/// 1. EventIntegrationHandler - Keyed singleton for processing integration events
|
||||
/// 2. AzureServiceBusEventListenerService - Hosted service for listening to event messages from Azure Service Bus
|
||||
/// for this integration type
|
||||
/// 3. AzureServiceBusIntegrationListenerService - Hosted service for listening to integration messages from
|
||||
/// Azure Service Bus for this integration type
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The handler uses the listener configuration's routing key as its service key, allowing multiple
|
||||
/// handlers to be registered for different integration types.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Service Bus processor options (PrefetchCount and MaxConcurrentCalls) are configured from the listener
|
||||
/// configuration to optimize message throughput and concurrency.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
internal static IServiceCollection AddAzureServiceBusIntegration<TConfig, TListenerConfig>(this IServiceCollection services,
|
||||
TListenerConfig listenerConfiguration)
|
||||
where TConfig : class
|
||||
where TListenerConfig : IIntegrationListenerConfiguration
|
||||
{
|
||||
services.TryAddKeyedSingleton<IEventMessageHandler>(serviceKey: listenerConfiguration.RoutingKey, implementationFactory: (provider, _) =>
|
||||
new EventIntegrationHandler<TConfig>(
|
||||
integrationType: listenerConfiguration.IntegrationType,
|
||||
eventIntegrationPublisher: provider.GetRequiredService<IEventIntegrationPublisher>(),
|
||||
integrationFilterService: provider.GetRequiredService<IIntegrationFilterService>(),
|
||||
cache: provider.GetRequiredKeyedService<IFusionCache>(EventIntegrationsCacheConstants.CacheName),
|
||||
configurationRepository: provider.GetRequiredService<IOrganizationIntegrationConfigurationRepository>(),
|
||||
groupRepository: provider.GetRequiredService<IGroupRepository>(),
|
||||
organizationRepository: provider.GetRequiredService<IOrganizationRepository>(),
|
||||
organizationUserRepository: provider.GetRequiredService<IOrganizationUserRepository>(), logger: provider.GetRequiredService<ILogger<EventIntegrationHandler<TConfig>>>())
|
||||
);
|
||||
services.TryAddEnumerable(ServiceDescriptor.Singleton<IHostedService,
|
||||
AzureServiceBusEventListenerService<TListenerConfig>>(provider =>
|
||||
new AzureServiceBusEventListenerService<TListenerConfig>(
|
||||
configuration: listenerConfiguration,
|
||||
handler: provider.GetRequiredKeyedService<IEventMessageHandler>(serviceKey: listenerConfiguration.RoutingKey),
|
||||
serviceBusService: provider.GetRequiredService<IAzureServiceBusService>(),
|
||||
serviceBusOptions: new ServiceBusProcessorOptions()
|
||||
{
|
||||
PrefetchCount = listenerConfiguration.EventPrefetchCount,
|
||||
MaxConcurrentCalls = listenerConfiguration.EventMaxConcurrentCalls
|
||||
},
|
||||
loggerFactory: provider.GetRequiredService<ILoggerFactory>()
|
||||
)
|
||||
)
|
||||
);
|
||||
services.TryAddEnumerable(ServiceDescriptor.Singleton<IHostedService,
|
||||
AzureServiceBusIntegrationListenerService<TListenerConfig>>(provider =>
|
||||
new AzureServiceBusIntegrationListenerService<TListenerConfig>(
|
||||
configuration: listenerConfiguration,
|
||||
handler: provider.GetRequiredService<IIntegrationHandler<TConfig>>(),
|
||||
serviceBusService: provider.GetRequiredService<IAzureServiceBusService>(),
|
||||
serviceBusOptions: new ServiceBusProcessorOptions()
|
||||
{
|
||||
PrefetchCount = listenerConfiguration.IntegrationPrefetchCount,
|
||||
MaxConcurrentCalls = listenerConfiguration.IntegrationMaxConcurrentCalls
|
||||
},
|
||||
loggerFactory: provider.GetRequiredService<ILoggerFactory>()
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers RabbitMQ-based event integration listeners for a specific integration type.
|
||||
/// </summary>
|
||||
/// <typeparam name="TConfig">The integration configuration details type (e.g., SlackIntegrationConfigurationDetails).</typeparam>
|
||||
/// <typeparam name="TListenerConfig">The listener configuration type implementing IIntegrationListenerConfiguration.</typeparam>
|
||||
/// <param name="services">The service collection to add services to.</param>
|
||||
/// <param name="listenerConfiguration">The listener configuration containing routing keys and message processing settings.</param>
|
||||
/// <returns>The service collection for chaining.</returns>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This method registers three key components:
|
||||
/// 1. EventIntegrationHandler - Keyed singleton for processing integration events
|
||||
/// 2. RabbitMqEventListenerService - Hosted service for listening to event messages from RabbitMQ for
|
||||
/// this integration type
|
||||
/// 3. RabbitMqIntegrationListenerService - Hosted service for listening to integration messages from RabbitMQ for
|
||||
/// this integration type
|
||||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// The handler uses the listener configuration's routing key as its service key, allowing multiple
|
||||
/// handlers to be registered for different integration types.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
internal static IServiceCollection AddRabbitMqIntegration<TConfig, TListenerConfig>(this IServiceCollection services,
|
||||
TListenerConfig listenerConfiguration)
|
||||
where TConfig : class
|
||||
where TListenerConfig : IIntegrationListenerConfiguration
|
||||
{
|
||||
services.TryAddKeyedSingleton<IEventMessageHandler>(serviceKey: listenerConfiguration.RoutingKey, implementationFactory: (provider, _) =>
|
||||
new EventIntegrationHandler<TConfig>(
|
||||
integrationType: listenerConfiguration.IntegrationType,
|
||||
eventIntegrationPublisher: provider.GetRequiredService<IEventIntegrationPublisher>(),
|
||||
integrationFilterService: provider.GetRequiredService<IIntegrationFilterService>(),
|
||||
cache: provider.GetRequiredKeyedService<IFusionCache>(EventIntegrationsCacheConstants.CacheName),
|
||||
configurationRepository: provider.GetRequiredService<IOrganizationIntegrationConfigurationRepository>(),
|
||||
groupRepository: provider.GetRequiredService<IGroupRepository>(),
|
||||
organizationRepository: provider.GetRequiredService<IOrganizationRepository>(),
|
||||
organizationUserRepository: provider.GetRequiredService<IOrganizationUserRepository>(), logger: provider.GetRequiredService<ILogger<EventIntegrationHandler<TConfig>>>())
|
||||
);
|
||||
services.TryAddEnumerable(ServiceDescriptor.Singleton<IHostedService,
|
||||
RabbitMqEventListenerService<TListenerConfig>>(provider =>
|
||||
new RabbitMqEventListenerService<TListenerConfig>(
|
||||
handler: provider.GetRequiredKeyedService<IEventMessageHandler>(serviceKey: listenerConfiguration.RoutingKey),
|
||||
configuration: listenerConfiguration,
|
||||
rabbitMqService: provider.GetRequiredService<IRabbitMqService>(),
|
||||
loggerFactory: provider.GetRequiredService<ILoggerFactory>()
|
||||
)
|
||||
)
|
||||
);
|
||||
services.TryAddEnumerable(ServiceDescriptor.Singleton<IHostedService,
|
||||
RabbitMqIntegrationListenerService<TListenerConfig>>(provider =>
|
||||
new RabbitMqIntegrationListenerService<TListenerConfig>(
|
||||
handler: provider.GetRequiredService<IIntegrationHandler<TConfig>>(),
|
||||
configuration: listenerConfiguration,
|
||||
rabbitMqService: provider.GetRequiredService<IRabbitMqService>(),
|
||||
loggerFactory: provider.GetRequiredService<ILoggerFactory>(),
|
||||
timeProvider: provider.GetRequiredService<TimeProvider>()
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
return services;
|
||||
}
|
||||
@@ -35,4 +510,50 @@ public static class EventIntegrationsServiceCollectionExtensions
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
internal static IServiceCollection AddOrganizationIntegrationConfigurationCommandsQueries(this IServiceCollection services)
|
||||
{
|
||||
services.TryAddScoped<ICreateOrganizationIntegrationConfigurationCommand, CreateOrganizationIntegrationConfigurationCommand>();
|
||||
services.TryAddScoped<IUpdateOrganizationIntegrationConfigurationCommand, UpdateOrganizationIntegrationConfigurationCommand>();
|
||||
services.TryAddScoped<IDeleteOrganizationIntegrationConfigurationCommand, DeleteOrganizationIntegrationConfigurationCommand>();
|
||||
services.TryAddScoped<IGetOrganizationIntegrationConfigurationsQuery, GetOrganizationIntegrationConfigurationsQuery>();
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if RabbitMQ is enabled for event integrations based on configuration settings.
|
||||
/// </summary>
|
||||
/// <param name="settings">The global settings containing RabbitMQ configuration.</param>
|
||||
/// <returns>True if all required RabbitMQ settings are present; otherwise, false.</returns>
|
||||
/// <remarks>
|
||||
/// Requires all the following settings to be configured:
|
||||
/// - EventLogging.RabbitMq.HostName
|
||||
/// - EventLogging.RabbitMq.Username
|
||||
/// - EventLogging.RabbitMq.Password
|
||||
/// - EventLogging.RabbitMq.EventExchangeName
|
||||
/// </remarks>
|
||||
internal static bool IsRabbitMqEnabled(GlobalSettings settings)
|
||||
{
|
||||
return CoreHelpers.SettingHasValue(settings.EventLogging.RabbitMq.HostName) &&
|
||||
CoreHelpers.SettingHasValue(settings.EventLogging.RabbitMq.Username) &&
|
||||
CoreHelpers.SettingHasValue(settings.EventLogging.RabbitMq.Password) &&
|
||||
CoreHelpers.SettingHasValue(settings.EventLogging.RabbitMq.EventExchangeName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if Azure Service Bus is enabled for event integrations based on configuration settings.
|
||||
/// </summary>
|
||||
/// <param name="settings">The global settings containing Azure Service Bus configuration.</param>
|
||||
/// <returns>True if all required Azure Service Bus settings are present; otherwise, false.</returns>
|
||||
/// <remarks>
|
||||
/// Requires both of the following settings to be configured:
|
||||
/// - EventLogging.AzureServiceBus.ConnectionString
|
||||
/// - EventLogging.AzureServiceBus.EventTopicName
|
||||
/// </remarks>
|
||||
internal static bool IsAzureServiceBusEnabled(GlobalSettings settings)
|
||||
{
|
||||
return CoreHelpers.SettingHasValue(settings.EventLogging.AzureServiceBus.ConnectionString) &&
|
||||
CoreHelpers.SettingHasValue(settings.EventLogging.AzureServiceBus.EventTopicName);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations.Interfaces;
|
||||
using Bit.Core.AdminConsole.Services;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Utilities;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using ZiggyCreatures.Caching.Fusion;
|
||||
|
||||
namespace Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations;
|
||||
|
||||
/// <summary>
|
||||
/// Command implementation for creating organization integration configurations with validation and cache invalidation support.
|
||||
/// </summary>
|
||||
public class CreateOrganizationIntegrationConfigurationCommand(
|
||||
IOrganizationIntegrationRepository integrationRepository,
|
||||
IOrganizationIntegrationConfigurationRepository configurationRepository,
|
||||
[FromKeyedServices(EventIntegrationsCacheConstants.CacheName)] IFusionCache cache,
|
||||
IOrganizationIntegrationConfigurationValidator validator)
|
||||
: ICreateOrganizationIntegrationConfigurationCommand
|
||||
{
|
||||
public async Task<OrganizationIntegrationConfiguration> CreateAsync(
|
||||
Guid organizationId,
|
||||
Guid integrationId,
|
||||
OrganizationIntegrationConfiguration configuration)
|
||||
{
|
||||
var integration = await integrationRepository.GetByIdAsync(integrationId);
|
||||
if (integration == null || integration.OrganizationId != organizationId)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
if (!validator.ValidateConfiguration(integration.Type, configuration))
|
||||
{
|
||||
throw new BadRequestException(
|
||||
$"Invalid Configuration and/or Filters for integration type {integration.Type}");
|
||||
}
|
||||
|
||||
var created = await configurationRepository.CreateAsync(configuration);
|
||||
|
||||
// Invalidate the cached configuration details
|
||||
// Even though this is a new record, the cache could hold a stale empty list for this
|
||||
if (created.EventType == null)
|
||||
{
|
||||
// Wildcard configuration - invalidate all cached results for this org/integration
|
||||
await cache.RemoveByTagAsync(
|
||||
EventIntegrationsCacheConstants.BuildCacheTagForOrganizationIntegration(
|
||||
organizationId: organizationId,
|
||||
integrationType: integration.Type
|
||||
));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Specific event type - only invalidate that specific cache entry
|
||||
await cache.RemoveAsync(
|
||||
EventIntegrationsCacheConstants.BuildCacheKeyForOrganizationIntegrationConfigurationDetails(
|
||||
organizationId: organizationId,
|
||||
integrationType: integration.Type,
|
||||
eventType: created.EventType.Value
|
||||
));
|
||||
}
|
||||
|
||||
return created;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
using Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations.Interfaces;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Utilities;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using ZiggyCreatures.Caching.Fusion;
|
||||
|
||||
namespace Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations;
|
||||
|
||||
/// <summary>
|
||||
/// Command implementation for deleting organization integration configurations with cache invalidation support.
|
||||
/// </summary>
|
||||
public class DeleteOrganizationIntegrationConfigurationCommand(
|
||||
IOrganizationIntegrationRepository integrationRepository,
|
||||
IOrganizationIntegrationConfigurationRepository configurationRepository,
|
||||
[FromKeyedServices(EventIntegrationsCacheConstants.CacheName)] IFusionCache cache)
|
||||
: IDeleteOrganizationIntegrationConfigurationCommand
|
||||
{
|
||||
public async Task DeleteAsync(Guid organizationId, Guid integrationId, Guid configurationId)
|
||||
{
|
||||
var integration = await integrationRepository.GetByIdAsync(integrationId);
|
||||
if (integration == null || integration.OrganizationId != organizationId)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
var configuration = await configurationRepository.GetByIdAsync(configurationId);
|
||||
if (configuration is null || configuration.OrganizationIntegrationId != integrationId)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
await configurationRepository.DeleteAsync(configuration);
|
||||
|
||||
if (configuration.EventType == null)
|
||||
{
|
||||
// Wildcard configuration - invalidate all cached results for this org/integration
|
||||
await cache.RemoveByTagAsync(
|
||||
EventIntegrationsCacheConstants.BuildCacheTagForOrganizationIntegration(
|
||||
organizationId: organizationId,
|
||||
integrationType: integration.Type
|
||||
));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Specific event type - only invalidate that specific cache entry
|
||||
await cache.RemoveAsync(
|
||||
EventIntegrationsCacheConstants.BuildCacheKeyForOrganizationIntegrationConfigurationDetails(
|
||||
organizationId: organizationId,
|
||||
integrationType: integration.Type,
|
||||
eventType: configuration.EventType.Value
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations.Interfaces;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Repositories;
|
||||
|
||||
namespace Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations;
|
||||
|
||||
/// <summary>
|
||||
/// Query implementation for retrieving organization integration configurations.
|
||||
/// </summary>
|
||||
public class GetOrganizationIntegrationConfigurationsQuery(
|
||||
IOrganizationIntegrationRepository integrationRepository,
|
||||
IOrganizationIntegrationConfigurationRepository configurationRepository)
|
||||
: IGetOrganizationIntegrationConfigurationsQuery
|
||||
{
|
||||
public async Task<List<OrganizationIntegrationConfiguration>> GetManyByIntegrationAsync(
|
||||
Guid organizationId,
|
||||
Guid integrationId)
|
||||
{
|
||||
var integration = await integrationRepository.GetByIdAsync(integrationId);
|
||||
if (integration == null || integration.OrganizationId != organizationId)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
var configurations = await configurationRepository.GetManyByIntegrationAsync(integrationId);
|
||||
return configurations.ToList();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
|
||||
namespace Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations.Interfaces;
|
||||
|
||||
/// <summary>
|
||||
/// Command interface for creating organization integration configurations.
|
||||
/// </summary>
|
||||
public interface ICreateOrganizationIntegrationConfigurationCommand
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new configuration for an organization integration.
|
||||
/// </summary>
|
||||
/// <param name="organizationId">The unique identifier of the organization.</param>
|
||||
/// <param name="integrationId">The unique identifier of the integration.</param>
|
||||
/// <param name="configuration">The configuration to create.</param>
|
||||
/// <returns>The created configuration.</returns>
|
||||
/// <exception cref="Exceptions.NotFoundException">Thrown when the integration does not exist
|
||||
/// or does not belong to the specified organization.</exception>
|
||||
/// <exception cref="Exceptions.BadRequestException">Thrown when the configuration or filters
|
||||
/// are invalid for the integration type.</exception>
|
||||
Task<OrganizationIntegrationConfiguration> CreateAsync(Guid organizationId, Guid integrationId, OrganizationIntegrationConfiguration configuration);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
namespace Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations.Interfaces;
|
||||
|
||||
/// <summary>
|
||||
/// Command interface for deleting organization integration configurations.
|
||||
/// </summary>
|
||||
public interface IDeleteOrganizationIntegrationConfigurationCommand
|
||||
{
|
||||
/// <summary>
|
||||
/// Deletes a configuration from an organization integration.
|
||||
/// </summary>
|
||||
/// <param name="organizationId">The unique identifier of the organization.</param>
|
||||
/// <param name="integrationId">The unique identifier of the integration.</param>
|
||||
/// <param name="configurationId">The unique identifier of the configuration to delete.</param>
|
||||
/// <exception cref="Exceptions.NotFoundException">
|
||||
/// Thrown when the integration or configuration does not exist,
|
||||
/// or the integration does not belong to the specified organization,
|
||||
/// or the configuration does not belong to the specified integration.</exception>
|
||||
Task DeleteAsync(Guid organizationId, Guid integrationId, Guid configurationId);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
|
||||
namespace Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations.Interfaces;
|
||||
|
||||
/// <summary>
|
||||
/// Query interface for retrieving organization integration configurations.
|
||||
/// </summary>
|
||||
public interface IGetOrganizationIntegrationConfigurationsQuery
|
||||
{
|
||||
/// <summary>
|
||||
/// Retrieves all configurations for a specific organization integration.
|
||||
/// </summary>
|
||||
/// <param name="organizationId">The unique identifier of the organization.</param>
|
||||
/// <param name="integrationId">The unique identifier of the integration.</param>
|
||||
/// <returns>A list of configurations associated with the integration.</returns>
|
||||
/// <exception cref="Exceptions.NotFoundException">Thrown when the integration does not exist
|
||||
/// or does not belong to the specified organization.</exception>
|
||||
Task<List<OrganizationIntegrationConfiguration>> GetManyByIntegrationAsync(Guid organizationId, Guid integrationId);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
|
||||
namespace Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations.Interfaces;
|
||||
|
||||
/// <summary>
|
||||
/// Command interface for updating organization integration configurations.
|
||||
/// </summary>
|
||||
public interface IUpdateOrganizationIntegrationConfigurationCommand
|
||||
{
|
||||
/// <summary>
|
||||
/// Updates an existing configuration for an organization integration.
|
||||
/// </summary>
|
||||
/// <param name="organizationId">The unique identifier of the organization.</param>
|
||||
/// <param name="integrationId">The unique identifier of the integration.</param>
|
||||
/// <param name="configurationId">The unique identifier of the configuration to update.</param>
|
||||
/// <param name="updatedConfiguration">The updated configuration data.</param>
|
||||
/// <returns>The updated configuration.</returns>
|
||||
/// <exception cref="Exceptions.NotFoundException">
|
||||
/// Thrown when the integration or the configuration does not exist,
|
||||
/// or the integration does not belong to the specified organization,
|
||||
/// or the configuration does not belong to the specified integration.</exception>
|
||||
/// <exception cref="Exceptions.BadRequestException">Thrown when the configuration or filters
|
||||
/// are invalid for the integration type.</exception>
|
||||
Task<OrganizationIntegrationConfiguration> UpdateAsync(Guid organizationId, Guid integrationId, Guid configurationId, OrganizationIntegrationConfiguration updatedConfiguration);
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations.Interfaces;
|
||||
using Bit.Core.AdminConsole.Services;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Utilities;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using ZiggyCreatures.Caching.Fusion;
|
||||
|
||||
namespace Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations;
|
||||
|
||||
/// <summary>
|
||||
/// Command implementation for updating organization integration configurations with validation and cache invalidation support.
|
||||
/// </summary>
|
||||
public class UpdateOrganizationIntegrationConfigurationCommand(
|
||||
IOrganizationIntegrationRepository integrationRepository,
|
||||
IOrganizationIntegrationConfigurationRepository configurationRepository,
|
||||
[FromKeyedServices(EventIntegrationsCacheConstants.CacheName)] IFusionCache cache,
|
||||
IOrganizationIntegrationConfigurationValidator validator)
|
||||
: IUpdateOrganizationIntegrationConfigurationCommand
|
||||
{
|
||||
public async Task<OrganizationIntegrationConfiguration> UpdateAsync(
|
||||
Guid organizationId,
|
||||
Guid integrationId,
|
||||
Guid configurationId,
|
||||
OrganizationIntegrationConfiguration updatedConfiguration)
|
||||
{
|
||||
var integration = await integrationRepository.GetByIdAsync(integrationId);
|
||||
if (integration == null || integration.OrganizationId != organizationId)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
var configuration = await configurationRepository.GetByIdAsync(configurationId);
|
||||
if (configuration is null || configuration.OrganizationIntegrationId != integrationId)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
if (!validator.ValidateConfiguration(integration.Type, updatedConfiguration))
|
||||
{
|
||||
throw new BadRequestException($"Invalid Configuration and/or Filters for integration type {integration.Type}");
|
||||
}
|
||||
|
||||
updatedConfiguration.Id = configuration.Id;
|
||||
updatedConfiguration.CreationDate = configuration.CreationDate;
|
||||
await configurationRepository.ReplaceAsync(updatedConfiguration);
|
||||
|
||||
// If either old or new EventType is null (wildcard), invalidate all cached results
|
||||
// for the specific integration
|
||||
if (configuration.EventType == null || updatedConfiguration.EventType == null)
|
||||
{
|
||||
// Wildcard involved - invalidate all cached results for this org/integration
|
||||
await cache.RemoveByTagAsync(
|
||||
EventIntegrationsCacheConstants.BuildCacheTagForOrganizationIntegration(
|
||||
organizationId: organizationId,
|
||||
integrationType: integration.Type
|
||||
));
|
||||
|
||||
return updatedConfiguration;
|
||||
}
|
||||
|
||||
// Both are specific event types - invalidate specific cache entries
|
||||
await cache.RemoveAsync(
|
||||
EventIntegrationsCacheConstants.BuildCacheKeyForOrganizationIntegrationConfigurationDetails(
|
||||
organizationId: organizationId,
|
||||
integrationType: integration.Type,
|
||||
eventType: configuration.EventType.Value
|
||||
));
|
||||
|
||||
// If event type changed, also clear the new event type's cache
|
||||
if (configuration.EventType != updatedConfiguration.EventType)
|
||||
{
|
||||
await cache.RemoveAsync(
|
||||
EventIntegrationsCacheConstants.BuildCacheKeyForOrganizationIntegrationConfigurationDetails(
|
||||
organizationId: organizationId,
|
||||
integrationType: integration.Type,
|
||||
eventType: updatedConfiguration.EventType.Value
|
||||
));
|
||||
}
|
||||
|
||||
return updatedConfiguration;
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
using Bit.Core.AdminConsole.Models.Business;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces;
|
||||
using Bit.Core.AdminConsole.Repositories;
|
||||
using Bit.Core.Billing.Services;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
@@ -18,7 +19,7 @@ public class ImportOrganizationUsersAndGroupsCommand : IImportOrganizationUsersA
|
||||
{
|
||||
private readonly IOrganizationRepository _organizationRepository;
|
||||
private readonly IOrganizationUserRepository _organizationUserRepository;
|
||||
private readonly IPaymentService _paymentService;
|
||||
private readonly IStripePaymentService _paymentService;
|
||||
private readonly IGroupRepository _groupRepository;
|
||||
private readonly IEventService _eventService;
|
||||
private readonly IOrganizationService _organizationService;
|
||||
@@ -27,7 +28,7 @@ public class ImportOrganizationUsersAndGroupsCommand : IImportOrganizationUsersA
|
||||
|
||||
public ImportOrganizationUsersAndGroupsCommand(IOrganizationRepository organizationRepository,
|
||||
IOrganizationUserRepository organizationUserRepository,
|
||||
IPaymentService paymentService,
|
||||
IStripePaymentService paymentService,
|
||||
IGroupRepository groupRepository,
|
||||
IEventService eventService,
|
||||
IOrganizationService organizationService)
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.PasswordManager;
|
||||
using Bit.Core.AdminConsole.Utilities.Errors;
|
||||
using Bit.Core.AdminConsole.Utilities.Validation;
|
||||
using Bit.Core.Billing.Services;
|
||||
using Bit.Core.Models.Business;
|
||||
using Bit.Core.OrganizationFeatures.OrganizationSubscriptions.Interface;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
|
||||
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation;
|
||||
|
||||
@@ -15,7 +15,7 @@ public class InviteOrganizationUsersValidator(
|
||||
IOrganizationRepository organizationRepository,
|
||||
IInviteUsersPasswordManagerValidator inviteUsersPasswordManagerValidator,
|
||||
IUpdateSecretsManagerSubscriptionCommand secretsManagerSubscriptionCommand,
|
||||
IPaymentService paymentService) : IInviteUsersValidator
|
||||
IStripePaymentService paymentService) : IInviteUsersValidator
|
||||
{
|
||||
public async Task<ValidationResult<InviteOrganizationUsersValidationRequest>> ValidateAsync(
|
||||
InviteOrganizationUsersValidationRequest request)
|
||||
|
||||
@@ -9,8 +9,8 @@ using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.V
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.Provider;
|
||||
using Bit.Core.AdminConsole.Repositories;
|
||||
using Bit.Core.AdminConsole.Utilities.Validation;
|
||||
using Bit.Core.Billing.Services;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Settings;
|
||||
|
||||
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Validation.PasswordManager;
|
||||
@@ -22,7 +22,7 @@ public class InviteUsersPasswordManagerValidator(
|
||||
IInviteUsersEnvironmentValidator inviteUsersEnvironmentValidator,
|
||||
IInviteUsersOrganizationValidator inviteUsersOrganizationValidator,
|
||||
IProviderRepository providerRepository,
|
||||
IPaymentService paymentService,
|
||||
IStripePaymentService paymentService,
|
||||
IOrganizationRepository organizationRepository
|
||||
) : IInviteUsersPasswordManagerValidator
|
||||
{
|
||||
|
||||
@@ -8,6 +8,7 @@ using Bit.Core.Billing.Enums;
|
||||
using Bit.Core.Billing.Organizations.Models;
|
||||
using Bit.Core.Billing.Organizations.Services;
|
||||
using Bit.Core.Billing.Pricing;
|
||||
using Bit.Core.Billing.Services;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
@@ -33,7 +34,7 @@ public interface ICloudOrganizationSignUpCommand
|
||||
public class CloudOrganizationSignUpCommand(
|
||||
IOrganizationUserRepository organizationUserRepository,
|
||||
IOrganizationBillingService organizationBillingService,
|
||||
IPaymentService paymentService,
|
||||
IStripePaymentService paymentService,
|
||||
IPolicyService policyService,
|
||||
IOrganizationRepository organizationRepository,
|
||||
IOrganizationApiKeyRepository organizationApiKeyRepository,
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Organizations.Interfaces;
|
||||
using Bit.Core.Auth.Enums;
|
||||
using Bit.Core.Auth.Repositories;
|
||||
using Bit.Core.Billing.Services;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
@@ -12,13 +13,13 @@ public class OrganizationDeleteCommand : IOrganizationDeleteCommand
|
||||
{
|
||||
private readonly IApplicationCacheService _applicationCacheService;
|
||||
private readonly IOrganizationRepository _organizationRepository;
|
||||
private readonly IPaymentService _paymentService;
|
||||
private readonly IStripePaymentService _paymentService;
|
||||
private readonly ISsoConfigRepository _ssoConfigRepository;
|
||||
|
||||
public OrganizationDeleteCommand(
|
||||
IApplicationCacheService applicationCacheService,
|
||||
IOrganizationRepository organizationRepository,
|
||||
IPaymentService paymentService,
|
||||
IStripePaymentService paymentService,
|
||||
ISsoConfigRepository ssoConfigRepository)
|
||||
{
|
||||
_applicationCacheService = applicationCacheService;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.InviteUsers.Models;
|
||||
using Bit.Core.Billing.Services;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Repositories;
|
||||
@@ -39,7 +40,7 @@ public class ResellerClientOrganizationSignUpCommand : IResellerClientOrganizati
|
||||
private readonly IOrganizationUserRepository _organizationUserRepository;
|
||||
private readonly IEventService _eventService;
|
||||
private readonly ISendOrganizationInvitesCommand _sendOrganizationInvitesCommand;
|
||||
private readonly IPaymentService _paymentService;
|
||||
private readonly IStripePaymentService _paymentService;
|
||||
|
||||
public ResellerClientOrganizationSignUpCommand(
|
||||
IOrganizationRepository organizationRepository,
|
||||
@@ -48,7 +49,7 @@ public class ResellerClientOrganizationSignUpCommand : IResellerClientOrganizati
|
||||
IOrganizationUserRepository organizationUserRepository,
|
||||
IEventService eventService,
|
||||
ISendOrganizationInvitesCommand sendOrganizationInvitesCommand,
|
||||
IPaymentService paymentService)
|
||||
IStripePaymentService paymentService)
|
||||
{
|
||||
_organizationRepository = organizationRepository;
|
||||
_organizationApiKeyRepository = organizationApiKeyRepository;
|
||||
|
||||
@@ -30,7 +30,7 @@ public class SelfHostedOrganizationSignUpCommand : ISelfHostedOrganizationSignUp
|
||||
private readonly ILicensingService _licensingService;
|
||||
private readonly IPolicyService _policyService;
|
||||
private readonly IGlobalSettings _globalSettings;
|
||||
private readonly IPaymentService _paymentService;
|
||||
private readonly IStripePaymentService _paymentService;
|
||||
|
||||
public SelfHostedOrganizationSignUpCommand(
|
||||
IOrganizationRepository organizationRepository,
|
||||
@@ -44,7 +44,7 @@ public class SelfHostedOrganizationSignUpCommand : ISelfHostedOrganizationSignUp
|
||||
ILicensingService licensingService,
|
||||
IPolicyService policyService,
|
||||
IGlobalSettings globalSettings,
|
||||
IPaymentService paymentService)
|
||||
IStripePaymentService paymentService)
|
||||
{
|
||||
_organizationRepository = organizationRepository;
|
||||
_organizationUserRepository = organizationUserRepository;
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
using Bit.Core.AdminConsole.Models.Data.Organizations;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Organizations.Interfaces;
|
||||
using Bit.Core.Billing.Services;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Bit.Core.AdminConsole.OrganizationFeatures.Organizations;
|
||||
|
||||
public class UpdateOrganizationSubscriptionCommand(IPaymentService paymentService,
|
||||
public class UpdateOrganizationSubscriptionCommand(IStripePaymentService paymentService,
|
||||
IOrganizationRepository repository,
|
||||
TimeProvider timeProvider,
|
||||
ILogger<UpdateOrganizationSubscriptionCommand> logger) : IUpdateOrganizationSubscriptionCommand
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.Enums;
|
||||
|
||||
namespace Bit.Core.AdminConsole.Services;
|
||||
|
||||
public interface IOrganizationIntegrationConfigurationValidator
|
||||
{
|
||||
/// <summary>
|
||||
/// Validates that the configuration is valid for the given integration type. The configuration must
|
||||
/// include a Configuration that is valid for the type, valid Filters, and a non-empty Template
|
||||
/// to pass validation.
|
||||
/// </summary>
|
||||
/// <param name="integrationType">The type of integration</param>
|
||||
/// <param name="configuration">The OrganizationIntegrationConfiguration to validate</param>
|
||||
/// <returns>True if valid, false otherwise</returns>
|
||||
bool ValidateConfiguration(IntegrationType integrationType, OrganizationIntegrationConfiguration configuration);
|
||||
}
|
||||
@@ -8,7 +8,7 @@ namespace Bit.Core.Services;
|
||||
public class AzureQueueEventWriteService : AzureQueueService<IEvent>, IEventWriteService
|
||||
{
|
||||
public AzureQueueEventWriteService(GlobalSettings globalSettings) : base(
|
||||
new QueueClient(globalSettings.Events.ConnectionString, "event"),
|
||||
new QueueClient(globalSettings.Events.ConnectionString, globalSettings.Events.QueueName),
|
||||
JsonHelpers.IgnoreWritingNull)
|
||||
{ }
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ using Bit.Core.Billing.Constants;
|
||||
using Bit.Core.Billing.Enums;
|
||||
using Bit.Core.Billing.Extensions;
|
||||
using Bit.Core.Billing.Pricing;
|
||||
using Bit.Core.Billing.Services;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
@@ -47,7 +48,7 @@ public class OrganizationService : IOrganizationService
|
||||
private readonly IPushNotificationService _pushNotificationService;
|
||||
private readonly IEventService _eventService;
|
||||
private readonly IApplicationCacheService _applicationCacheService;
|
||||
private readonly IPaymentService _paymentService;
|
||||
private readonly IStripePaymentService _paymentService;
|
||||
private readonly IPolicyRepository _policyRepository;
|
||||
private readonly IPolicyService _policyService;
|
||||
private readonly ISsoUserRepository _ssoUserRepository;
|
||||
@@ -74,7 +75,7 @@ public class OrganizationService : IOrganizationService
|
||||
IPushNotificationService pushNotificationService,
|
||||
IEventService eventService,
|
||||
IApplicationCacheService applicationCacheService,
|
||||
IPaymentService paymentService,
|
||||
IStripePaymentService paymentService,
|
||||
IPolicyRepository policyRepository,
|
||||
IPolicyService policyService,
|
||||
ISsoUserRepository ssoUserRepository,
|
||||
@@ -358,7 +359,7 @@ public class OrganizationService : IOrganizationService
|
||||
{
|
||||
var newDisplayName = organization.DisplayName();
|
||||
|
||||
await _stripeAdapter.CustomerUpdateAsync(organization.GatewayCustomerId,
|
||||
await _stripeAdapter.UpdateCustomerAsync(organization.GatewayCustomerId,
|
||||
new CustomerUpdateOptions
|
||||
{
|
||||
Email = organization.BillingEmail,
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
using System.Text.Json;
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.Models.Data.EventIntegrations;
|
||||
using Bit.Core.Enums;
|
||||
|
||||
namespace Bit.Core.AdminConsole.Services;
|
||||
|
||||
public class OrganizationIntegrationConfigurationValidator : IOrganizationIntegrationConfigurationValidator
|
||||
{
|
||||
public bool ValidateConfiguration(IntegrationType integrationType,
|
||||
OrganizationIntegrationConfiguration configuration)
|
||||
{
|
||||
// Validate template is present
|
||||
if (string.IsNullOrWhiteSpace(configuration.Template))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// If Filters are present, they must be valid
|
||||
if (!IsFiltersValid(configuration.Filters))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (integrationType)
|
||||
{
|
||||
case IntegrationType.CloudBillingSync or IntegrationType.Scim:
|
||||
return false;
|
||||
case IntegrationType.Slack:
|
||||
return IsConfigurationValid<SlackIntegrationConfiguration>(configuration.Configuration);
|
||||
case IntegrationType.Webhook:
|
||||
return IsConfigurationValid<WebhookIntegrationConfiguration>(configuration.Configuration);
|
||||
case IntegrationType.Hec:
|
||||
case IntegrationType.Datadog:
|
||||
case IntegrationType.Teams:
|
||||
return configuration.Configuration is null;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsConfigurationValid<T>(string? configuration)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(configuration))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var config = JsonSerializer.Deserialize<T>(configuration);
|
||||
return config is not null;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsFiltersValid(string? filters)
|
||||
{
|
||||
if (filters is null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var filterGroup = JsonSerializer.Deserialize<IntegrationFilterGroup>(filters);
|
||||
return filterGroup is not null;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,8 +7,8 @@ using Bit.Core.Billing.Models;
|
||||
using Bit.Core.Billing.Organizations.Models;
|
||||
using Bit.Core.Billing.Payment.Models;
|
||||
using Bit.Core.Billing.Pricing;
|
||||
using Bit.Core.Billing.Services;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Services;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using OneOf;
|
||||
using Stripe;
|
||||
@@ -125,7 +125,7 @@ public class PreviewOrganizationTaxCommand(
|
||||
|
||||
options.SubscriptionDetails = new InvoiceSubscriptionDetailsOptions { Items = items };
|
||||
|
||||
var invoice = await stripeAdapter.InvoiceCreatePreviewAsync(options);
|
||||
var invoice = await stripeAdapter.CreateInvoicePreviewAsync(options);
|
||||
return GetAmounts(invoice);
|
||||
});
|
||||
|
||||
@@ -165,7 +165,7 @@ public class PreviewOrganizationTaxCommand(
|
||||
|
||||
options.SubscriptionDetails = new InvoiceSubscriptionDetailsOptions { Items = items };
|
||||
|
||||
var invoice = await stripeAdapter.InvoiceCreatePreviewAsync(options);
|
||||
var invoice = await stripeAdapter.CreateInvoicePreviewAsync(options);
|
||||
return GetAmounts(invoice);
|
||||
}
|
||||
else
|
||||
@@ -181,7 +181,7 @@ public class PreviewOrganizationTaxCommand(
|
||||
|
||||
var options = GetBaseOptions(billingAddress, planChange.Tier != ProductTierType.Families);
|
||||
|
||||
var subscription = await stripeAdapter.SubscriptionGetAsync(organization.GatewaySubscriptionId,
|
||||
var subscription = await stripeAdapter.GetSubscriptionAsync(organization.GatewaySubscriptionId,
|
||||
new SubscriptionGetOptions { Expand = ["customer"] });
|
||||
|
||||
if (subscription.Customer.Discount != null)
|
||||
@@ -259,7 +259,7 @@ public class PreviewOrganizationTaxCommand(
|
||||
|
||||
options.SubscriptionDetails = new InvoiceSubscriptionDetailsOptions { Items = items };
|
||||
|
||||
var invoice = await stripeAdapter.InvoiceCreatePreviewAsync(options);
|
||||
var invoice = await stripeAdapter.CreateInvoicePreviewAsync(options);
|
||||
return GetAmounts(invoice);
|
||||
}
|
||||
});
|
||||
@@ -278,7 +278,7 @@ public class PreviewOrganizationTaxCommand(
|
||||
return new BadRequest("Organization does not have a subscription.");
|
||||
}
|
||||
|
||||
var subscription = await stripeAdapter.SubscriptionGetAsync(organization.GatewaySubscriptionId,
|
||||
var subscription = await stripeAdapter.GetSubscriptionAsync(organization.GatewaySubscriptionId,
|
||||
new SubscriptionGetOptions { Expand = ["customer.tax_ids"] });
|
||||
|
||||
var options = GetBaseOptions(subscription.Customer,
|
||||
@@ -336,7 +336,7 @@ public class PreviewOrganizationTaxCommand(
|
||||
|
||||
options.SubscriptionDetails = new InvoiceSubscriptionDetailsOptions { Items = items };
|
||||
|
||||
var invoice = await stripeAdapter.InvoiceCreatePreviewAsync(options);
|
||||
var invoice = await stripeAdapter.CreateInvoicePreviewAsync(options);
|
||||
return GetAmounts(invoice);
|
||||
});
|
||||
|
||||
|
||||
@@ -22,14 +22,14 @@ public interface IGetCloudOrganizationLicenseQuery
|
||||
public class GetCloudOrganizationLicenseQuery : IGetCloudOrganizationLicenseQuery
|
||||
{
|
||||
private readonly IInstallationRepository _installationRepository;
|
||||
private readonly IPaymentService _paymentService;
|
||||
private readonly IStripePaymentService _paymentService;
|
||||
private readonly ILicensingService _licensingService;
|
||||
private readonly IProviderRepository _providerRepository;
|
||||
private readonly IFeatureService _featureService;
|
||||
|
||||
public GetCloudOrganizationLicenseQuery(
|
||||
IInstallationRepository installationRepository,
|
||||
IPaymentService paymentService,
|
||||
IStripePaymentService paymentService,
|
||||
ILicensingService licensingService,
|
||||
IProviderRepository providerRepository,
|
||||
IFeatureService featureService)
|
||||
|
||||
@@ -9,7 +9,6 @@ using Bit.Core.Billing.Organizations.Models;
|
||||
using Bit.Core.Billing.Payment.Queries;
|
||||
using Bit.Core.Billing.Services;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Services;
|
||||
using Stripe;
|
||||
using Stripe.Tax;
|
||||
|
||||
@@ -201,7 +200,7 @@ public class GetOrganizationWarningsQuery(
|
||||
// ReSharper disable once InvertIf
|
||||
if (subscription.Status == SubscriptionStatus.PastDue)
|
||||
{
|
||||
var openInvoices = await stripeAdapter.InvoiceSearchAsync(new InvoiceSearchOptions
|
||||
var openInvoices = await stripeAdapter.SearchInvoiceAsync(new InvoiceSearchOptions
|
||||
{
|
||||
Query = $"subscription:'{subscription.Id}' status:'open'"
|
||||
});
|
||||
@@ -257,8 +256,8 @@ public class GetOrganizationWarningsQuery(
|
||||
|
||||
// Get active and scheduled registrations
|
||||
var registrations = (await Task.WhenAll(
|
||||
stripeAdapter.TaxRegistrationsListAsync(new RegistrationListOptions { Status = TaxRegistrationStatus.Active }),
|
||||
stripeAdapter.TaxRegistrationsListAsync(new RegistrationListOptions { Status = TaxRegistrationStatus.Scheduled })))
|
||||
stripeAdapter.ListTaxRegistrationsAsync(new RegistrationListOptions { Status = TaxRegistrationStatus.Active }),
|
||||
stripeAdapter.ListTaxRegistrationsAsync(new RegistrationListOptions { Status = TaxRegistrationStatus.Scheduled })))
|
||||
.SelectMany(registrations => registrations.Data);
|
||||
|
||||
// Find the matching registration for the customer
|
||||
|
||||
@@ -14,7 +14,6 @@ using Bit.Core.Billing.Tax.Services;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Settings;
|
||||
using Braintree;
|
||||
using Microsoft.Extensions.Logging;
|
||||
@@ -161,7 +160,7 @@ public class OrganizationBillingService(
|
||||
try
|
||||
{
|
||||
// Update the subscription in Stripe
|
||||
await stripeAdapter.SubscriptionUpdateAsync(subscription.Id, updateOptions);
|
||||
await stripeAdapter.UpdateSubscriptionAsync(subscription.Id, updateOptions);
|
||||
organization.PlanType = newPlan.Type;
|
||||
await organizationRepository.ReplaceAsync(organization);
|
||||
}
|
||||
@@ -185,7 +184,7 @@ public class OrganizationBillingService(
|
||||
|
||||
var newDisplayName = organization.DisplayName();
|
||||
|
||||
await stripeAdapter.CustomerUpdateAsync(organization.GatewayCustomerId,
|
||||
await stripeAdapter.UpdateCustomerAsync(organization.GatewayCustomerId,
|
||||
new CustomerUpdateOptions
|
||||
{
|
||||
Email = organization.BillingEmail,
|
||||
@@ -324,7 +323,7 @@ public class OrganizationBillingService(
|
||||
case PaymentMethodType.BankAccount:
|
||||
{
|
||||
var setupIntent =
|
||||
(await stripeAdapter.SetupIntentList(new SetupIntentListOptions { PaymentMethod = paymentMethodToken }))
|
||||
(await stripeAdapter.ListSetupIntentsAsync(new SetupIntentListOptions { PaymentMethod = paymentMethodToken }))
|
||||
.FirstOrDefault();
|
||||
|
||||
if (setupIntent == null)
|
||||
@@ -358,7 +357,7 @@ public class OrganizationBillingService(
|
||||
|
||||
try
|
||||
{
|
||||
var customer = await stripeAdapter.CustomerCreateAsync(customerCreateOptions);
|
||||
var customer = await stripeAdapter.CreateCustomerAsync(customerCreateOptions);
|
||||
|
||||
organization.Gateway = GatewayType.Stripe;
|
||||
organization.GatewayCustomerId = customer.Id;
|
||||
@@ -509,7 +508,7 @@ public class OrganizationBillingService(
|
||||
subscriptionCreateOptions.AutomaticTax = new SubscriptionAutomaticTaxOptions { Enabled = true };
|
||||
}
|
||||
|
||||
var subscription = await stripeAdapter.SubscriptionCreateAsync(subscriptionCreateOptions);
|
||||
var subscription = await stripeAdapter.CreateSubscriptionAsync(subscriptionCreateOptions);
|
||||
|
||||
organization.GatewaySubscriptionId = subscription.Id;
|
||||
await organizationRepository.ReplaceAsync(organization);
|
||||
@@ -537,14 +536,14 @@ public class OrganizationBillingService(
|
||||
customer = customer switch
|
||||
{
|
||||
{ Address.Country: not Core.Constants.CountryAbbreviations.UnitedStates, TaxExempt: not StripeConstants.TaxExempt.Reverse } => await
|
||||
stripeAdapter.CustomerUpdateAsync(customer.Id,
|
||||
stripeAdapter.UpdateCustomerAsync(customer.Id,
|
||||
new CustomerUpdateOptions
|
||||
{
|
||||
Expand = expansions,
|
||||
TaxExempt = StripeConstants.TaxExempt.Reverse
|
||||
}),
|
||||
{ Address.Country: Core.Constants.CountryAbbreviations.UnitedStates, TaxExempt: StripeConstants.TaxExempt.Reverse } => await
|
||||
stripeAdapter.CustomerUpdateAsync(customer.Id,
|
||||
stripeAdapter.UpdateCustomerAsync(customer.Id,
|
||||
new CustomerUpdateOptions
|
||||
{
|
||||
Expand = expansions,
|
||||
@@ -603,7 +602,7 @@ public class OrganizationBillingService(
|
||||
}
|
||||
}
|
||||
};
|
||||
await stripeAdapter.SubscriptionUpdateAsync(organization.GatewaySubscriptionId, options);
|
||||
await stripeAdapter.UpdateSubscriptionAsync(organization.GatewaySubscriptionId, options);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ using Bit.Core.Billing.Extensions;
|
||||
using Bit.Core.Billing.Payment.Models;
|
||||
using Bit.Core.Billing.Services;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Services;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Stripe;
|
||||
|
||||
@@ -46,7 +45,7 @@ public class UpdateBillingAddressCommand(
|
||||
BillingAddress billingAddress)
|
||||
{
|
||||
var customer =
|
||||
await stripeAdapter.CustomerUpdateAsync(subscriber.GatewayCustomerId,
|
||||
await stripeAdapter.UpdateCustomerAsync(subscriber.GatewayCustomerId,
|
||||
new CustomerUpdateOptions
|
||||
{
|
||||
Address = new AddressOptions
|
||||
@@ -71,7 +70,7 @@ public class UpdateBillingAddressCommand(
|
||||
BillingAddress billingAddress)
|
||||
{
|
||||
var customer =
|
||||
await stripeAdapter.CustomerUpdateAsync(subscriber.GatewayCustomerId,
|
||||
await stripeAdapter.UpdateCustomerAsync(subscriber.GatewayCustomerId,
|
||||
new CustomerUpdateOptions
|
||||
{
|
||||
Address = new AddressOptions
|
||||
@@ -92,7 +91,7 @@ public class UpdateBillingAddressCommand(
|
||||
await EnableAutomaticTaxAsync(subscriber, customer);
|
||||
|
||||
var deleteExistingTaxIds = customer.TaxIds?.Any() ?? false
|
||||
? customer.TaxIds.Select(taxId => stripeAdapter.TaxIdDeleteAsync(customer.Id, taxId.Id)).ToList()
|
||||
? customer.TaxIds.Select(taxId => stripeAdapter.DeleteTaxIdAsync(customer.Id, taxId.Id)).ToList()
|
||||
: [];
|
||||
|
||||
if (billingAddress.TaxId == null)
|
||||
@@ -101,12 +100,12 @@ public class UpdateBillingAddressCommand(
|
||||
return BillingAddress.From(customer.Address);
|
||||
}
|
||||
|
||||
var updatedTaxId = await stripeAdapter.TaxIdCreateAsync(customer.Id,
|
||||
var updatedTaxId = await stripeAdapter.CreateTaxIdAsync(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,
|
||||
updatedTaxId = await stripeAdapter.CreateTaxIdAsync(customer.Id,
|
||||
new TaxIdCreateOptions
|
||||
{
|
||||
Type = StripeConstants.TaxIdType.EUVAT,
|
||||
@@ -130,7 +129,7 @@ public class UpdateBillingAddressCommand(
|
||||
|
||||
if (subscription is { AutomaticTax.Enabled: false })
|
||||
{
|
||||
await stripeAdapter.SubscriptionUpdateAsync(subscriber.GatewaySubscriptionId,
|
||||
await stripeAdapter.UpdateSubscriptionAsync(subscriber.GatewaySubscriptionId,
|
||||
new SubscriptionUpdateOptions
|
||||
{
|
||||
AutomaticTax = new SubscriptionAutomaticTaxOptions { Enabled = true }
|
||||
|
||||
@@ -4,7 +4,6 @@ using Bit.Core.Billing.Constants;
|
||||
using Bit.Core.Billing.Payment.Models;
|
||||
using Bit.Core.Billing.Services;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Settings;
|
||||
using Bit.Core.Utilities;
|
||||
using Braintree;
|
||||
@@ -56,7 +55,7 @@ public class UpdatePaymentMethodCommand(
|
||||
|
||||
if (billingAddress != null && customer.Address is not { Country: not null, PostalCode: not null })
|
||||
{
|
||||
await stripeAdapter.CustomerUpdateAsync(customer.Id,
|
||||
await stripeAdapter.UpdateCustomerAsync(customer.Id,
|
||||
new CustomerUpdateOptions
|
||||
{
|
||||
Address = new AddressOptions
|
||||
@@ -75,7 +74,7 @@ public class UpdatePaymentMethodCommand(
|
||||
Customer customer,
|
||||
string token)
|
||||
{
|
||||
var setupIntents = await stripeAdapter.SetupIntentList(new SetupIntentListOptions
|
||||
var setupIntents = await stripeAdapter.ListSetupIntentsAsync(new SetupIntentListOptions
|
||||
{
|
||||
Expand = ["data.payment_method"],
|
||||
PaymentMethod = token
|
||||
@@ -104,9 +103,9 @@ public class UpdatePaymentMethodCommand(
|
||||
Customer customer,
|
||||
string token)
|
||||
{
|
||||
var paymentMethod = await stripeAdapter.PaymentMethodAttachAsync(token, new PaymentMethodAttachOptions { Customer = customer.Id });
|
||||
var paymentMethod = await stripeAdapter.AttachPaymentMethodAsync(token, new PaymentMethodAttachOptions { Customer = customer.Id });
|
||||
|
||||
await stripeAdapter.CustomerUpdateAsync(customer.Id,
|
||||
await stripeAdapter.UpdateCustomerAsync(customer.Id,
|
||||
new CustomerUpdateOptions
|
||||
{
|
||||
InvoiceSettings = new CustomerInvoiceSettingsOptions { DefaultPaymentMethod = token }
|
||||
@@ -139,7 +138,7 @@ public class UpdatePaymentMethodCommand(
|
||||
[StripeConstants.MetadataKeys.BraintreeCustomerId] = braintreeCustomer.Id
|
||||
};
|
||||
|
||||
await stripeAdapter.CustomerUpdateAsync(customer.Id, new CustomerUpdateOptions { Metadata = metadata });
|
||||
await stripeAdapter.UpdateCustomerAsync(customer.Id, new CustomerUpdateOptions { Metadata = metadata });
|
||||
}
|
||||
|
||||
var payPalAccount = braintreeCustomer.DefaultPaymentMethod as PayPalAccount;
|
||||
@@ -204,7 +203,7 @@ public class UpdatePaymentMethodCommand(
|
||||
[StripeConstants.MetadataKeys.BraintreeCustomerId] = string.Empty
|
||||
};
|
||||
|
||||
await stripeAdapter.CustomerUpdateAsync(customer.Id, new CustomerUpdateOptions { Metadata = metadata });
|
||||
await stripeAdapter.UpdateCustomerAsync(customer.Id, new CustomerUpdateOptions { Metadata = metadata });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ using Bit.Core.Billing.Extensions;
|
||||
using Bit.Core.Billing.Payment.Models;
|
||||
using Bit.Core.Billing.Services;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Services;
|
||||
using Braintree;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Stripe;
|
||||
@@ -53,7 +52,7 @@ public class GetPaymentMethodQuery(
|
||||
|
||||
if (!string.IsNullOrEmpty(setupIntentId))
|
||||
{
|
||||
var setupIntent = await stripeAdapter.SetupIntentGet(setupIntentId, new SetupIntentGetOptions
|
||||
var setupIntent = await stripeAdapter.GetSetupIntentAsync(setupIntentId, new SetupIntentGetOptions
|
||||
{
|
||||
Expand = ["payment_method"]
|
||||
});
|
||||
|
||||
@@ -3,7 +3,6 @@ using Bit.Core.Billing.Constants;
|
||||
using Bit.Core.Billing.Extensions;
|
||||
using Bit.Core.Billing.Services;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Services;
|
||||
using Stripe;
|
||||
|
||||
namespace Bit.Core.Billing.Payment.Queries;
|
||||
@@ -48,7 +47,7 @@ public class HasPaymentMethodQuery(
|
||||
return false;
|
||||
}
|
||||
|
||||
var setupIntent = await stripeAdapter.SetupIntentGet(setupIntentId, new SetupIntentGetOptions
|
||||
var setupIntent = await stripeAdapter.GetSetupIntentAsync(setupIntentId, new SetupIntentGetOptions
|
||||
{
|
||||
Expand = ["payment_method"]
|
||||
});
|
||||
|
||||
@@ -210,7 +210,7 @@ public class CreatePremiumCloudHostedSubscriptionCommand(
|
||||
case TokenizablePaymentMethodType.BankAccount:
|
||||
{
|
||||
var setupIntent =
|
||||
(await stripeAdapter.SetupIntentList(new SetupIntentListOptions { PaymentMethod = tokenizedPaymentMethod.Token }))
|
||||
(await stripeAdapter.ListSetupIntentsAsync(new SetupIntentListOptions { PaymentMethod = tokenizedPaymentMethod.Token }))
|
||||
.FirstOrDefault();
|
||||
|
||||
if (setupIntent == null)
|
||||
@@ -243,7 +243,7 @@ public class CreatePremiumCloudHostedSubscriptionCommand(
|
||||
|
||||
try
|
||||
{
|
||||
return await stripeAdapter.CustomerCreateAsync(customerCreateOptions);
|
||||
return await stripeAdapter.CreateCustomerAsync(customerCreateOptions);
|
||||
}
|
||||
catch
|
||||
{
|
||||
@@ -300,7 +300,7 @@ public class CreatePremiumCloudHostedSubscriptionCommand(
|
||||
ValidateLocation = ValidateTaxLocationTiming.Immediately
|
||||
}
|
||||
};
|
||||
return await stripeAdapter.CustomerUpdateAsync(customer.Id, options);
|
||||
return await stripeAdapter.UpdateCustomerAsync(customer.Id, options);
|
||||
}
|
||||
|
||||
private async Task<Subscription> CreateSubscriptionAsync(
|
||||
@@ -349,11 +349,11 @@ public class CreatePremiumCloudHostedSubscriptionCommand(
|
||||
OffSession = true
|
||||
};
|
||||
|
||||
var subscription = await stripeAdapter.SubscriptionCreateAsync(subscriptionCreateOptions);
|
||||
var subscription = await stripeAdapter.CreateSubscriptionAsync(subscriptionCreateOptions);
|
||||
|
||||
if (usingPayPal)
|
||||
{
|
||||
await stripeAdapter.InvoiceUpdateAsync(subscription.LatestInvoiceId, new InvoiceUpdateOptions
|
||||
await stripeAdapter.UpdateInvoiceAsync(subscription.LatestInvoiceId, new InvoiceUpdateOptions
|
||||
{
|
||||
AutoAdvance = false
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using Bit.Core.Billing.Commands;
|
||||
using Bit.Core.Billing.Payment.Models;
|
||||
using Bit.Core.Billing.Pricing;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Billing.Services;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Stripe;
|
||||
|
||||
@@ -56,7 +56,7 @@ public class PreviewPremiumTaxCommand(
|
||||
});
|
||||
}
|
||||
|
||||
var invoice = await stripeAdapter.InvoiceCreatePreviewAsync(options);
|
||||
var invoice = await stripeAdapter.CreateInvoicePreviewAsync(options);
|
||||
return GetAmounts(invoice);
|
||||
});
|
||||
|
||||
|
||||
50
src/Core/Billing/Services/IStripeAdapter.cs
Normal file
50
src/Core/Billing/Services/IStripeAdapter.cs
Normal file
@@ -0,0 +1,50 @@
|
||||
// FIXME: Update this file to be null safe and then delete the line below
|
||||
#nullable disable
|
||||
|
||||
using Bit.Core.Models.BitStripe;
|
||||
using Stripe;
|
||||
using Stripe.Tax;
|
||||
|
||||
namespace Bit.Core.Billing.Services;
|
||||
|
||||
public interface IStripeAdapter
|
||||
{
|
||||
Task<Customer> CreateCustomerAsync(CustomerCreateOptions customerCreateOptions);
|
||||
Task<Customer> GetCustomerAsync(string id, CustomerGetOptions options = null);
|
||||
Task<Customer> UpdateCustomerAsync(string id, CustomerUpdateOptions options = null);
|
||||
Task<Customer> DeleteCustomerAsync(string id);
|
||||
Task<List<PaymentMethod>> ListCustomerPaymentMethodsAsync(string id, CustomerPaymentMethodListOptions options = null);
|
||||
Task<CustomerBalanceTransaction> CreateCustomerBalanceTransactionAsync(string customerId,
|
||||
CustomerBalanceTransactionCreateOptions options);
|
||||
Task<Subscription> CreateSubscriptionAsync(SubscriptionCreateOptions subscriptionCreateOptions);
|
||||
Task<Subscription> GetSubscriptionAsync(string id, SubscriptionGetOptions options = null);
|
||||
Task<StripeList<Registration>> ListTaxRegistrationsAsync(RegistrationListOptions options = null);
|
||||
Task DeleteCustomerDiscountAsync(string customerId, CustomerDeleteDiscountOptions options = null);
|
||||
Task<Subscription> UpdateSubscriptionAsync(string id, SubscriptionUpdateOptions options = null);
|
||||
Task<Subscription> CancelSubscriptionAsync(string id, SubscriptionCancelOptions options = null);
|
||||
Task<Invoice> GetInvoiceAsync(string id, InvoiceGetOptions options);
|
||||
Task<List<Invoice>> ListInvoicesAsync(StripeInvoiceListOptions options);
|
||||
Task<Invoice> CreateInvoicePreviewAsync(InvoiceCreatePreviewOptions options);
|
||||
Task<List<Invoice>> SearchInvoiceAsync(InvoiceSearchOptions options);
|
||||
Task<Invoice> UpdateInvoiceAsync(string id, InvoiceUpdateOptions options);
|
||||
Task<Invoice> FinalizeInvoiceAsync(string id, InvoiceFinalizeOptions options);
|
||||
Task<Invoice> SendInvoiceAsync(string id, InvoiceSendOptions options);
|
||||
Task<Invoice> PayInvoiceAsync(string id, InvoicePayOptions options = null);
|
||||
Task<Invoice> DeleteInvoiceAsync(string id, InvoiceDeleteOptions options = null);
|
||||
Task<Invoice> VoidInvoiceAsync(string id, InvoiceVoidOptions options = null);
|
||||
IEnumerable<PaymentMethod> ListPaymentMethodsAutoPaging(PaymentMethodListOptions options);
|
||||
IAsyncEnumerable<PaymentMethod> ListPaymentMethodsAutoPagingAsync(PaymentMethodListOptions options);
|
||||
Task<PaymentMethod> AttachPaymentMethodAsync(string id, PaymentMethodAttachOptions options = null);
|
||||
Task<PaymentMethod> DetachPaymentMethodAsync(string id, PaymentMethodDetachOptions options = null);
|
||||
Task<TaxId> CreateTaxIdAsync(string id, TaxIdCreateOptions options);
|
||||
Task<TaxId> DeleteTaxIdAsync(string customerId, string taxIdId, TaxIdDeleteOptions options = null);
|
||||
Task<StripeList<Charge>> ListChargesAsync(ChargeListOptions options);
|
||||
Task<Refund> CreateRefundAsync(RefundCreateOptions options);
|
||||
Task<Card> DeleteCardAsync(string customerId, string cardId, CardDeleteOptions options = null);
|
||||
Task<BankAccount> DeleteBankAccountAsync(string customerId, string bankAccount, BankAccountDeleteOptions options = null);
|
||||
Task<SetupIntent> CreateSetupIntentAsync(SetupIntentCreateOptions options);
|
||||
Task<List<SetupIntent>> ListSetupIntentsAsync(SetupIntentListOptions options);
|
||||
Task CancelSetupIntentAsync(string id, SetupIntentCancelOptions options = null);
|
||||
Task<SetupIntent> GetSetupIntentAsync(string id, SetupIntentGetOptions options = null);
|
||||
Task<Price> GetPriceAsync(string id, PriceGetOptions options = null);
|
||||
}
|
||||
@@ -8,9 +8,9 @@ using Bit.Core.Entities;
|
||||
using Bit.Core.Models.Business;
|
||||
using Bit.Core.Models.StaticStore;
|
||||
|
||||
namespace Bit.Core.Services;
|
||||
namespace Bit.Core.Billing.Services;
|
||||
|
||||
public interface IPaymentService
|
||||
public interface IStripePaymentService
|
||||
{
|
||||
Task CancelAndRecoverChargesAsync(ISubscriber subscriber);
|
||||
Task SponsorOrganizationAsync(Organization org, OrganizationSponsorship sponsorship);
|
||||
6
src/Core/Billing/Services/IStripeSyncService.cs
Normal file
6
src/Core/Billing/Services/IStripeSyncService.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace Bit.Core.Billing.Services;
|
||||
|
||||
public interface IStripeSyncService
|
||||
{
|
||||
Task UpdateCustomerEmailAddressAsync(string gatewayCustomerId, string emailAddress);
|
||||
}
|
||||
@@ -4,7 +4,6 @@ using Bit.Core.Billing.Models;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Models.BitStripe;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
|
||||
namespace Bit.Core.Billing.Services.Implementations;
|
||||
|
||||
@@ -23,7 +22,7 @@ public class PaymentHistoryService(
|
||||
return Array.Empty<BillingHistoryInfo.BillingInvoice>();
|
||||
}
|
||||
|
||||
var invoices = await stripeAdapter.InvoiceListAsync(new StripeInvoiceListOptions
|
||||
var invoices = await stripeAdapter.ListInvoicesAsync(new StripeInvoiceListOptions
|
||||
{
|
||||
Customer = subscriber.GatewayCustomerId,
|
||||
Limit = pageSize,
|
||||
|
||||
@@ -12,7 +12,6 @@ using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Settings;
|
||||
using Braintree;
|
||||
using Microsoft.Extensions.Logging;
|
||||
@@ -68,7 +67,7 @@ public class PremiumUserBillingService(
|
||||
}
|
||||
};
|
||||
|
||||
customer = await stripeAdapter.CustomerCreateAsync(options);
|
||||
customer = await stripeAdapter.CreateCustomerAsync(options);
|
||||
|
||||
user.Gateway = GatewayType.Stripe;
|
||||
user.GatewayCustomerId = customer.Id;
|
||||
@@ -81,7 +80,7 @@ public class PremiumUserBillingService(
|
||||
Balance = customer.Balance + credit
|
||||
};
|
||||
|
||||
await stripeAdapter.CustomerUpdateAsync(customer.Id, options);
|
||||
await stripeAdapter.UpdateCustomerAsync(customer.Id, options);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -227,7 +226,7 @@ public class PremiumUserBillingService(
|
||||
case PaymentMethodType.BankAccount:
|
||||
{
|
||||
var setupIntent =
|
||||
(await stripeAdapter.SetupIntentList(new SetupIntentListOptions { PaymentMethod = paymentMethodToken }))
|
||||
(await stripeAdapter.ListSetupIntentsAsync(new SetupIntentListOptions { PaymentMethod = paymentMethodToken }))
|
||||
.FirstOrDefault();
|
||||
|
||||
if (setupIntent == null)
|
||||
@@ -260,7 +259,7 @@ public class PremiumUserBillingService(
|
||||
|
||||
try
|
||||
{
|
||||
return await stripeAdapter.CustomerCreateAsync(customerCreateOptions);
|
||||
return await stripeAdapter.CreateCustomerAsync(customerCreateOptions);
|
||||
}
|
||||
catch (StripeException stripeException) when (stripeException.StripeError?.Code ==
|
||||
StripeConstants.ErrorCodes.CustomerTaxLocationInvalid)
|
||||
@@ -347,11 +346,11 @@ public class PremiumUserBillingService(
|
||||
OffSession = true
|
||||
};
|
||||
|
||||
var subscription = await stripeAdapter.SubscriptionCreateAsync(subscriptionCreateOptions);
|
||||
var subscription = await stripeAdapter.CreateSubscriptionAsync(subscriptionCreateOptions);
|
||||
|
||||
if (usingPayPal)
|
||||
{
|
||||
await stripeAdapter.InvoiceUpdateAsync(subscription.LatestInvoiceId, new InvoiceUpdateOptions
|
||||
await stripeAdapter.UpdateInvoiceAsync(subscription.LatestInvoiceId, new InvoiceUpdateOptions
|
||||
{
|
||||
AutoAdvance = false
|
||||
});
|
||||
@@ -387,6 +386,6 @@ public class PremiumUserBillingService(
|
||||
}
|
||||
};
|
||||
|
||||
return await stripeAdapter.CustomerUpdateAsync(customer.Id, options);
|
||||
return await stripeAdapter.UpdateCustomerAsync(customer.Id, options);
|
||||
}
|
||||
}
|
||||
|
||||
209
src/Core/Billing/Services/Implementations/StripeAdapter.cs
Normal file
209
src/Core/Billing/Services/Implementations/StripeAdapter.cs
Normal file
@@ -0,0 +1,209 @@
|
||||
// FIXME: Update this file to be null safe and then delete the line below
|
||||
|
||||
#nullable disable
|
||||
|
||||
using Bit.Core.Models.BitStripe;
|
||||
using Stripe;
|
||||
using Stripe.Tax;
|
||||
using Stripe.TestHelpers;
|
||||
using CustomerService = Stripe.CustomerService;
|
||||
using RefundService = Stripe.RefundService;
|
||||
|
||||
namespace Bit.Core.Billing.Services.Implementations;
|
||||
|
||||
public class StripeAdapter : IStripeAdapter
|
||||
{
|
||||
private readonly CustomerService _customerService;
|
||||
private readonly SubscriptionService _subscriptionService;
|
||||
private readonly InvoiceService _invoiceService;
|
||||
private readonly PaymentMethodService _paymentMethodService;
|
||||
private readonly TaxIdService _taxIdService;
|
||||
private readonly ChargeService _chargeService;
|
||||
private readonly RefundService _refundService;
|
||||
private readonly CardService _cardService;
|
||||
private readonly BankAccountService _bankAccountService;
|
||||
private readonly PriceService _priceService;
|
||||
private readonly SetupIntentService _setupIntentService;
|
||||
private readonly TestClockService _testClockService;
|
||||
private readonly CustomerBalanceTransactionService _customerBalanceTransactionService;
|
||||
private readonly RegistrationService _taxRegistrationService;
|
||||
|
||||
public StripeAdapter()
|
||||
{
|
||||
_customerService = new CustomerService();
|
||||
_subscriptionService = new SubscriptionService();
|
||||
_invoiceService = new InvoiceService();
|
||||
_paymentMethodService = new PaymentMethodService();
|
||||
_taxIdService = new TaxIdService();
|
||||
_chargeService = new ChargeService();
|
||||
_refundService = new RefundService();
|
||||
_cardService = new CardService();
|
||||
_bankAccountService = new BankAccountService();
|
||||
_priceService = new PriceService();
|
||||
_setupIntentService = new SetupIntentService();
|
||||
_testClockService = new TestClockService();
|
||||
_customerBalanceTransactionService = new CustomerBalanceTransactionService();
|
||||
_taxRegistrationService = new RegistrationService();
|
||||
}
|
||||
|
||||
/**************
|
||||
** CUSTOMER **
|
||||
**************/
|
||||
public Task<Customer> CreateCustomerAsync(CustomerCreateOptions options) =>
|
||||
_customerService.CreateAsync(options);
|
||||
|
||||
public Task DeleteCustomerDiscountAsync(string customerId, CustomerDeleteDiscountOptions options = null) =>
|
||||
_customerService.DeleteDiscountAsync(customerId, options);
|
||||
|
||||
public Task<Customer> GetCustomerAsync(string id, CustomerGetOptions options = null) =>
|
||||
_customerService.GetAsync(id, options);
|
||||
|
||||
public Task<Customer> UpdateCustomerAsync(string id, CustomerUpdateOptions options = null) =>
|
||||
_customerService.UpdateAsync(id, options);
|
||||
|
||||
public Task<Customer> DeleteCustomerAsync(string id) =>
|
||||
_customerService.DeleteAsync(id);
|
||||
|
||||
public async Task<List<PaymentMethod>> ListCustomerPaymentMethodsAsync(string id,
|
||||
CustomerPaymentMethodListOptions options = null)
|
||||
{
|
||||
var paymentMethods = await _customerService.ListPaymentMethodsAsync(id, options);
|
||||
return paymentMethods.Data;
|
||||
}
|
||||
|
||||
public Task<CustomerBalanceTransaction> CreateCustomerBalanceTransactionAsync(string customerId,
|
||||
CustomerBalanceTransactionCreateOptions options) =>
|
||||
_customerBalanceTransactionService.CreateAsync(customerId, options);
|
||||
|
||||
/******************
|
||||
** SUBSCRIPTION **
|
||||
******************/
|
||||
public Task<Subscription> CreateSubscriptionAsync(SubscriptionCreateOptions options) =>
|
||||
_subscriptionService.CreateAsync(options);
|
||||
|
||||
public Task<Subscription> GetSubscriptionAsync(string id, SubscriptionGetOptions options = null) =>
|
||||
_subscriptionService.GetAsync(id, options);
|
||||
|
||||
public Task<Subscription> UpdateSubscriptionAsync(string id,
|
||||
SubscriptionUpdateOptions options = null) =>
|
||||
_subscriptionService.UpdateAsync(id, options);
|
||||
|
||||
public Task<Subscription> CancelSubscriptionAsync(string id, SubscriptionCancelOptions options = null) =>
|
||||
_subscriptionService.CancelAsync(id, options);
|
||||
|
||||
/*************
|
||||
** INVOICE **
|
||||
*************/
|
||||
public Task<Invoice> GetInvoiceAsync(string id, InvoiceGetOptions options) =>
|
||||
_invoiceService.GetAsync(id, options);
|
||||
|
||||
public async Task<List<Invoice>> ListInvoicesAsync(StripeInvoiceListOptions options)
|
||||
{
|
||||
if (!options.SelectAll)
|
||||
{
|
||||
return (await _invoiceService.ListAsync(options.ToInvoiceListOptions())).Data;
|
||||
}
|
||||
|
||||
options.Limit = 100;
|
||||
|
||||
var invoices = new List<Invoice>();
|
||||
|
||||
await foreach (var invoice in _invoiceService.ListAutoPagingAsync(options.ToInvoiceListOptions()))
|
||||
{
|
||||
invoices.Add(invoice);
|
||||
}
|
||||
|
||||
return invoices;
|
||||
}
|
||||
|
||||
public Task<Invoice> CreateInvoicePreviewAsync(InvoiceCreatePreviewOptions options) =>
|
||||
_invoiceService.CreatePreviewAsync(options);
|
||||
|
||||
public async Task<List<Invoice>> SearchInvoiceAsync(InvoiceSearchOptions options) =>
|
||||
(await _invoiceService.SearchAsync(options)).Data;
|
||||
|
||||
public Task<Invoice> UpdateInvoiceAsync(string id, InvoiceUpdateOptions options) =>
|
||||
_invoiceService.UpdateAsync(id, options);
|
||||
|
||||
public Task<Invoice> FinalizeInvoiceAsync(string id, InvoiceFinalizeOptions options) =>
|
||||
_invoiceService.FinalizeInvoiceAsync(id, options);
|
||||
|
||||
public Task<Invoice> SendInvoiceAsync(string id, InvoiceSendOptions options) =>
|
||||
_invoiceService.SendInvoiceAsync(id, options);
|
||||
|
||||
public Task<Invoice> PayInvoiceAsync(string id, InvoicePayOptions options = null) =>
|
||||
_invoiceService.PayAsync(id, options);
|
||||
|
||||
public Task<Invoice> DeleteInvoiceAsync(string id, InvoiceDeleteOptions options = null) =>
|
||||
_invoiceService.DeleteAsync(id, options);
|
||||
|
||||
public Task<Invoice> VoidInvoiceAsync(string id, InvoiceVoidOptions options = null) =>
|
||||
_invoiceService.VoidInvoiceAsync(id, options);
|
||||
|
||||
/********************
|
||||
** PAYMENT METHOD **
|
||||
********************/
|
||||
public IEnumerable<PaymentMethod> ListPaymentMethodsAutoPaging(PaymentMethodListOptions options) =>
|
||||
_paymentMethodService.ListAutoPaging(options);
|
||||
|
||||
public IAsyncEnumerable<PaymentMethod> ListPaymentMethodsAutoPagingAsync(PaymentMethodListOptions options)
|
||||
=> _paymentMethodService.ListAutoPagingAsync(options);
|
||||
|
||||
public Task<PaymentMethod> AttachPaymentMethodAsync(string id, PaymentMethodAttachOptions options = null) =>
|
||||
_paymentMethodService.AttachAsync(id, options);
|
||||
|
||||
public Task<PaymentMethod> DetachPaymentMethodAsync(string id, PaymentMethodDetachOptions options = null) =>
|
||||
_paymentMethodService.DetachAsync(id, options);
|
||||
|
||||
/************
|
||||
** TAX ID **
|
||||
************/
|
||||
public Task<TaxId> CreateTaxIdAsync(string id, TaxIdCreateOptions options) =>
|
||||
_taxIdService.CreateAsync(id, options);
|
||||
|
||||
public Task<TaxId> DeleteTaxIdAsync(string customerId, string taxIdId,
|
||||
TaxIdDeleteOptions options = null) =>
|
||||
_taxIdService.DeleteAsync(customerId, taxIdId, options);
|
||||
|
||||
/******************
|
||||
** BANK ACCOUNT **
|
||||
******************/
|
||||
public Task<BankAccount> DeleteBankAccountAsync(string customerId, string bankAccount, BankAccountDeleteOptions options = null) =>
|
||||
_bankAccountService.DeleteAsync(customerId, bankAccount, options);
|
||||
|
||||
/***********
|
||||
** PRICE **
|
||||
***********/
|
||||
public Task<Price> GetPriceAsync(string id, PriceGetOptions options = null) =>
|
||||
_priceService.GetAsync(id, options);
|
||||
|
||||
/******************
|
||||
** SETUP INTENT **
|
||||
******************/
|
||||
public Task<SetupIntent> CreateSetupIntentAsync(SetupIntentCreateOptions options) =>
|
||||
_setupIntentService.CreateAsync(options);
|
||||
|
||||
public async Task<List<SetupIntent>> ListSetupIntentsAsync(SetupIntentListOptions options) =>
|
||||
(await _setupIntentService.ListAsync(options)).Data;
|
||||
|
||||
public Task CancelSetupIntentAsync(string id, SetupIntentCancelOptions options = null) =>
|
||||
_setupIntentService.CancelAsync(id, options);
|
||||
|
||||
public Task<SetupIntent> GetSetupIntentAsync(string id, SetupIntentGetOptions options = null) =>
|
||||
_setupIntentService.GetAsync(id, options);
|
||||
|
||||
/*******************
|
||||
** MISCELLANEOUS **
|
||||
*******************/
|
||||
public Task<StripeList<Charge>> ListChargesAsync(ChargeListOptions options) =>
|
||||
_chargeService.ListAsync(options);
|
||||
|
||||
public Task<StripeList<Registration>> ListTaxRegistrationsAsync(RegistrationListOptions options = null) =>
|
||||
_taxRegistrationService.ListAsync(options);
|
||||
|
||||
public Task<Refund> CreateRefundAsync(RefundCreateOptions options) =>
|
||||
_refundService.CreateAsync(options);
|
||||
|
||||
public Task<Card> DeleteCardAsync(string customerId, string cardId, CardDeleteOptions options = null) =>
|
||||
_cardService.DeleteAsync(customerId, cardId, options);
|
||||
}
|
||||
@@ -21,9 +21,9 @@ using Stripe;
|
||||
using PaymentMethod = Stripe.PaymentMethod;
|
||||
using StaticStore = Bit.Core.Models.StaticStore;
|
||||
|
||||
namespace Bit.Core.Services;
|
||||
namespace Bit.Core.Billing.Services.Implementations;
|
||||
|
||||
public class StripePaymentService : IPaymentService
|
||||
public class StripePaymentService : IStripePaymentService
|
||||
{
|
||||
private const string SecretsManagerStandaloneDiscountId = "sm-standalone";
|
||||
|
||||
@@ -64,7 +64,7 @@ public class StripePaymentService : IPaymentService
|
||||
|
||||
await FinalizeSubscriptionChangeAsync(org, subscriptionUpdate, true);
|
||||
|
||||
var sub = await _stripeAdapter.SubscriptionGetAsync(org.GatewaySubscriptionId);
|
||||
var sub = await _stripeAdapter.GetSubscriptionAsync(org.GatewaySubscriptionId);
|
||||
org.ExpirationDate = sub.GetCurrentPeriodEnd();
|
||||
|
||||
if (sponsorship is not null)
|
||||
@@ -84,7 +84,7 @@ public class StripePaymentService : IPaymentService
|
||||
{
|
||||
// remember, when in doubt, throw
|
||||
var subGetOptions = new SubscriptionGetOptions { Expand = ["customer.tax", "customer.tax_ids"] };
|
||||
var sub = await _stripeAdapter.SubscriptionGetAsync(subscriber.GatewaySubscriptionId, subGetOptions);
|
||||
var sub = await _stripeAdapter.GetSubscriptionAsync(subscriber.GatewaySubscriptionId, subGetOptions);
|
||||
if (sub == null)
|
||||
{
|
||||
throw new GatewayException("Subscription not found.");
|
||||
@@ -107,7 +107,7 @@ public class StripePaymentService : IPaymentService
|
||||
var subUpdateOptions = new SubscriptionUpdateOptions
|
||||
{
|
||||
Items = updatedItemOptions,
|
||||
ProrationBehavior = invoiceNow ? Constants.AlwaysInvoice : Constants.CreateProrations,
|
||||
ProrationBehavior = invoiceNow ? Core.Constants.AlwaysInvoice : Core.Constants.CreateProrations,
|
||||
DaysUntilDue = daysUntilDue ?? 1,
|
||||
CollectionMethod = "send_invoice"
|
||||
};
|
||||
@@ -121,11 +121,11 @@ public class StripePaymentService : IPaymentService
|
||||
{
|
||||
if (sub.Customer is
|
||||
{
|
||||
Address.Country: not Constants.CountryAbbreviations.UnitedStates,
|
||||
Address.Country: not Core.Constants.CountryAbbreviations.UnitedStates,
|
||||
TaxExempt: not StripeConstants.TaxExempt.Reverse
|
||||
})
|
||||
{
|
||||
await _stripeAdapter.CustomerUpdateAsync(sub.CustomerId,
|
||||
await _stripeAdapter.UpdateCustomerAsync(sub.CustomerId,
|
||||
new CustomerUpdateOptions { TaxExempt = StripeConstants.TaxExempt.Reverse });
|
||||
}
|
||||
|
||||
@@ -141,9 +141,9 @@ public class StripePaymentService : IPaymentService
|
||||
string paymentIntentClientSecret = null;
|
||||
try
|
||||
{
|
||||
var subResponse = await _stripeAdapter.SubscriptionUpdateAsync(sub.Id, subUpdateOptions);
|
||||
var subResponse = await _stripeAdapter.UpdateSubscriptionAsync(sub.Id, subUpdateOptions);
|
||||
|
||||
var invoice = await _stripeAdapter.InvoiceGetAsync(subResponse?.LatestInvoiceId, new InvoiceGetOptions());
|
||||
var invoice = await _stripeAdapter.GetInvoiceAsync(subResponse?.LatestInvoiceId, new InvoiceGetOptions());
|
||||
if (invoice == null)
|
||||
{
|
||||
throw new BadRequestException("Unable to locate draft invoice for subscription update.");
|
||||
@@ -162,9 +162,9 @@ public class StripePaymentService : IPaymentService
|
||||
}
|
||||
else
|
||||
{
|
||||
invoice = await _stripeAdapter.InvoiceFinalizeInvoiceAsync(subResponse.LatestInvoiceId,
|
||||
invoice = await _stripeAdapter.FinalizeInvoiceAsync(subResponse.LatestInvoiceId,
|
||||
new InvoiceFinalizeOptions { AutoAdvance = false, });
|
||||
await _stripeAdapter.InvoiceSendInvoiceAsync(invoice.Id, new InvoiceSendOptions());
|
||||
await _stripeAdapter.SendInvoiceAsync(invoice.Id, new InvoiceSendOptions());
|
||||
paymentIntentClientSecret = null;
|
||||
}
|
||||
}
|
||||
@@ -172,7 +172,7 @@ public class StripePaymentService : IPaymentService
|
||||
catch
|
||||
{
|
||||
// Need to revert the subscription
|
||||
await _stripeAdapter.SubscriptionUpdateAsync(sub.Id, new SubscriptionUpdateOptions
|
||||
await _stripeAdapter.UpdateSubscriptionAsync(sub.Id, new SubscriptionUpdateOptions
|
||||
{
|
||||
Items = subscriptionUpdate.RevertItemsOptions(sub),
|
||||
// This proration behavior prevents a false "credit" from
|
||||
@@ -187,7 +187,7 @@ public class StripePaymentService : IPaymentService
|
||||
else if (invoice.Status != StripeConstants.InvoiceStatus.Paid)
|
||||
{
|
||||
// Pay invoice with no charge to the customer this completes the invoice immediately without waiting the scheduled 1h
|
||||
invoice = await _stripeAdapter.InvoicePayAsync(subResponse.LatestInvoiceId);
|
||||
invoice = await _stripeAdapter.PayInvoiceAsync(subResponse.LatestInvoiceId);
|
||||
paymentIntentClientSecret = null;
|
||||
}
|
||||
}
|
||||
@@ -196,7 +196,7 @@ public class StripePaymentService : IPaymentService
|
||||
// Change back the subscription collection method and/or days until due
|
||||
if (collectionMethod != "send_invoice" || daysUntilDue == null)
|
||||
{
|
||||
await _stripeAdapter.SubscriptionUpdateAsync(sub.Id,
|
||||
await _stripeAdapter.UpdateSubscriptionAsync(sub.Id,
|
||||
new SubscriptionUpdateOptions
|
||||
{
|
||||
CollectionMethod = collectionMethod,
|
||||
@@ -204,14 +204,14 @@ public class StripePaymentService : IPaymentService
|
||||
});
|
||||
}
|
||||
|
||||
var customer = await _stripeAdapter.CustomerGetAsync(sub.CustomerId);
|
||||
var customer = await _stripeAdapter.GetCustomerAsync(sub.CustomerId);
|
||||
|
||||
var newCoupon = customer.Discount?.Coupon?.Id;
|
||||
|
||||
if (!string.IsNullOrEmpty(existingCoupon) && string.IsNullOrEmpty(newCoupon))
|
||||
{
|
||||
// Re-add the lost coupon due to the update.
|
||||
await _stripeAdapter.SubscriptionUpdateAsync(sub.Id, new SubscriptionUpdateOptions
|
||||
await _stripeAdapter.UpdateSubscriptionAsync(sub.Id, new SubscriptionUpdateOptions
|
||||
{
|
||||
Discounts =
|
||||
[
|
||||
@@ -284,7 +284,7 @@ public class StripePaymentService : IPaymentService
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(subscriber.GatewaySubscriptionId))
|
||||
{
|
||||
await _stripeAdapter.SubscriptionCancelAsync(subscriber.GatewaySubscriptionId,
|
||||
await _stripeAdapter.CancelSubscriptionAsync(subscriber.GatewaySubscriptionId,
|
||||
new SubscriptionCancelOptions());
|
||||
}
|
||||
|
||||
@@ -293,7 +293,7 @@ public class StripePaymentService : IPaymentService
|
||||
return;
|
||||
}
|
||||
|
||||
var customer = await _stripeAdapter.CustomerGetAsync(subscriber.GatewayCustomerId);
|
||||
var customer = await _stripeAdapter.GetCustomerAsync(subscriber.GatewayCustomerId);
|
||||
if (customer == null)
|
||||
{
|
||||
return;
|
||||
@@ -318,7 +318,7 @@ public class StripePaymentService : IPaymentService
|
||||
}
|
||||
else
|
||||
{
|
||||
var charges = await _stripeAdapter.ChargeListAsync(new ChargeListOptions
|
||||
var charges = await _stripeAdapter.ListChargesAsync(new ChargeListOptions
|
||||
{
|
||||
Customer = subscriber.GatewayCustomerId
|
||||
});
|
||||
@@ -327,12 +327,12 @@ public class StripePaymentService : IPaymentService
|
||||
{
|
||||
foreach (var charge in charges.Data.Where(c => c.Captured && !c.Refunded))
|
||||
{
|
||||
await _stripeAdapter.RefundCreateAsync(new RefundCreateOptions { Charge = charge.Id });
|
||||
await _stripeAdapter.CreateRefundAsync(new RefundCreateOptions { Charge = charge.Id });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await _stripeAdapter.CustomerDeleteAsync(subscriber.GatewayCustomerId);
|
||||
await _stripeAdapter.DeleteCustomerAsync(subscriber.GatewayCustomerId);
|
||||
}
|
||||
|
||||
public async Task<string> PayInvoiceAfterSubscriptionChangeAsync(ISubscriber subscriber, Invoice invoice)
|
||||
@@ -340,7 +340,7 @@ public class StripePaymentService : IPaymentService
|
||||
var customerOptions = new CustomerGetOptions();
|
||||
customerOptions.AddExpand("default_source");
|
||||
customerOptions.AddExpand("invoice_settings.default_payment_method");
|
||||
var customer = await _stripeAdapter.CustomerGetAsync(subscriber.GatewayCustomerId, customerOptions);
|
||||
var customer = await _stripeAdapter.GetCustomerAsync(subscriber.GatewayCustomerId, customerOptions);
|
||||
|
||||
string paymentIntentClientSecret = null;
|
||||
|
||||
@@ -360,13 +360,13 @@ public class StripePaymentService : IPaymentService
|
||||
// We're going to delete this draft invoice, it can't be paid
|
||||
try
|
||||
{
|
||||
await _stripeAdapter.InvoiceDeleteAsync(invoice.Id);
|
||||
await _stripeAdapter.DeleteInvoiceAsync(invoice.Id);
|
||||
}
|
||||
catch
|
||||
{
|
||||
await _stripeAdapter.InvoiceFinalizeInvoiceAsync(invoice.Id,
|
||||
await _stripeAdapter.FinalizeInvoiceAsync(invoice.Id,
|
||||
new InvoiceFinalizeOptions { AutoAdvance = false });
|
||||
await _stripeAdapter.InvoiceVoidInvoiceAsync(invoice.Id);
|
||||
await _stripeAdapter.VoidInvoiceAsync(invoice.Id);
|
||||
}
|
||||
|
||||
throw new BadRequestException("No payment method is available.");
|
||||
@@ -379,7 +379,7 @@ public class StripePaymentService : IPaymentService
|
||||
{
|
||||
// Finalize the invoice (from Draft) w/o auto-advance so we
|
||||
// can attempt payment manually.
|
||||
invoice = await _stripeAdapter.InvoiceFinalizeInvoiceAsync(invoice.Id,
|
||||
invoice = await _stripeAdapter.FinalizeInvoiceAsync(invoice.Id,
|
||||
new InvoiceFinalizeOptions { AutoAdvance = false, });
|
||||
var invoicePayOptions = new InvoicePayOptions { PaymentMethod = cardPaymentMethodId, };
|
||||
if (customer?.Metadata?.ContainsKey("btCustomerId") ?? false)
|
||||
@@ -414,7 +414,7 @@ public class StripePaymentService : IPaymentService
|
||||
}
|
||||
|
||||
braintreeTransaction = transactionResult.Target;
|
||||
invoice = await _stripeAdapter.InvoiceUpdateAsync(invoice.Id, new InvoiceUpdateOptions
|
||||
invoice = await _stripeAdapter.UpdateInvoiceAsync(invoice.Id, new InvoiceUpdateOptions
|
||||
{
|
||||
Metadata = new Dictionary<string, string>
|
||||
{
|
||||
@@ -428,7 +428,7 @@ public class StripePaymentService : IPaymentService
|
||||
|
||||
try
|
||||
{
|
||||
invoice = await _stripeAdapter.InvoicePayAsync(invoice.Id, invoicePayOptions);
|
||||
invoice = await _stripeAdapter.PayInvoiceAsync(invoice.Id, invoicePayOptions);
|
||||
}
|
||||
catch (StripeException e)
|
||||
{
|
||||
@@ -438,7 +438,7 @@ public class StripePaymentService : IPaymentService
|
||||
// SCA required, get intent client secret
|
||||
var invoiceGetOptions = new InvoiceGetOptions();
|
||||
invoiceGetOptions.AddExpand("confirmation_secret");
|
||||
invoice = await _stripeAdapter.InvoiceGetAsync(invoice.Id, invoiceGetOptions);
|
||||
invoice = await _stripeAdapter.GetInvoiceAsync(invoice.Id, invoiceGetOptions);
|
||||
paymentIntentClientSecret = invoice?.ConfirmationSecret?.ClientSecret;
|
||||
}
|
||||
else
|
||||
@@ -462,7 +462,7 @@ public class StripePaymentService : IPaymentService
|
||||
return paymentIntentClientSecret;
|
||||
}
|
||||
|
||||
invoice = await _stripeAdapter.InvoiceVoidInvoiceAsync(invoice.Id, new InvoiceVoidOptions());
|
||||
invoice = await _stripeAdapter.VoidInvoiceAsync(invoice.Id, new InvoiceVoidOptions());
|
||||
|
||||
// HACK: Workaround for customer balance credit
|
||||
if (invoice.StartingBalance < 0)
|
||||
@@ -470,12 +470,12 @@ public class StripePaymentService : IPaymentService
|
||||
// Customer had a balance applied to this invoice. Since we can't fully trust Stripe to
|
||||
// credit it back to the customer (even though their docs claim they will), we need to
|
||||
// check that balance against the current customer balance and determine if it needs to be re-applied
|
||||
customer = await _stripeAdapter.CustomerGetAsync(subscriber.GatewayCustomerId, customerOptions);
|
||||
customer = await _stripeAdapter.GetCustomerAsync(subscriber.GatewayCustomerId, customerOptions);
|
||||
|
||||
// Assumption: Customer balance should now be $0, otherwise payment would not have failed.
|
||||
if (customer.Balance == 0)
|
||||
{
|
||||
await _stripeAdapter.CustomerUpdateAsync(customer.Id,
|
||||
await _stripeAdapter.UpdateCustomerAsync(customer.Id,
|
||||
new CustomerUpdateOptions { Balance = invoice.StartingBalance });
|
||||
}
|
||||
}
|
||||
@@ -506,7 +506,7 @@ public class StripePaymentService : IPaymentService
|
||||
throw new GatewayException("No subscription.");
|
||||
}
|
||||
|
||||
var sub = await _stripeAdapter.SubscriptionGetAsync(subscriber.GatewaySubscriptionId);
|
||||
var sub = await _stripeAdapter.GetSubscriptionAsync(subscriber.GatewaySubscriptionId);
|
||||
if (sub == null)
|
||||
{
|
||||
throw new GatewayException("Subscription was not found.");
|
||||
@@ -522,9 +522,9 @@ public class StripePaymentService : IPaymentService
|
||||
try
|
||||
{
|
||||
var canceledSub = endOfPeriod
|
||||
? await _stripeAdapter.SubscriptionUpdateAsync(sub.Id,
|
||||
? await _stripeAdapter.UpdateSubscriptionAsync(sub.Id,
|
||||
new SubscriptionUpdateOptions { CancelAtPeriodEnd = true })
|
||||
: await _stripeAdapter.SubscriptionCancelAsync(sub.Id, new SubscriptionCancelOptions());
|
||||
: await _stripeAdapter.CancelSubscriptionAsync(sub.Id, new SubscriptionCancelOptions());
|
||||
if (!canceledSub.CanceledAt.HasValue)
|
||||
{
|
||||
throw new GatewayException("Unable to cancel subscription.");
|
||||
@@ -551,7 +551,7 @@ public class StripePaymentService : IPaymentService
|
||||
throw new GatewayException("No subscription.");
|
||||
}
|
||||
|
||||
var sub = await _stripeAdapter.SubscriptionGetAsync(subscriber.GatewaySubscriptionId);
|
||||
var sub = await _stripeAdapter.GetSubscriptionAsync(subscriber.GatewaySubscriptionId);
|
||||
if (sub == null)
|
||||
{
|
||||
throw new GatewayException("Subscription was not found.");
|
||||
@@ -563,7 +563,7 @@ public class StripePaymentService : IPaymentService
|
||||
throw new GatewayException("Subscription is not marked for cancellation.");
|
||||
}
|
||||
|
||||
var updatedSub = await _stripeAdapter.SubscriptionUpdateAsync(sub.Id,
|
||||
var updatedSub = await _stripeAdapter.UpdateSubscriptionAsync(sub.Id,
|
||||
new SubscriptionUpdateOptions { CancelAtPeriodEnd = false });
|
||||
if (updatedSub.CanceledAt.HasValue)
|
||||
{
|
||||
@@ -578,11 +578,11 @@ public class StripePaymentService : IPaymentService
|
||||
!string.IsNullOrWhiteSpace(subscriber.GatewayCustomerId);
|
||||
if (customerExists)
|
||||
{
|
||||
customer = await _stripeAdapter.CustomerGetAsync(subscriber.GatewayCustomerId);
|
||||
customer = await _stripeAdapter.GetCustomerAsync(subscriber.GatewayCustomerId);
|
||||
}
|
||||
else
|
||||
{
|
||||
customer = await _stripeAdapter.CustomerCreateAsync(new CustomerCreateOptions
|
||||
customer = await _stripeAdapter.CreateCustomerAsync(new CustomerCreateOptions
|
||||
{
|
||||
Email = subscriber.BillingEmailAddress(),
|
||||
Description = subscriber.BillingName(),
|
||||
@@ -591,9 +591,8 @@ public class StripePaymentService : IPaymentService
|
||||
subscriber.GatewayCustomerId = customer.Id;
|
||||
}
|
||||
|
||||
await _stripeAdapter.CustomerUpdateAsync(customer.Id,
|
||||
await _stripeAdapter.UpdateCustomerAsync(customer.Id,
|
||||
new CustomerUpdateOptions { Balance = customer.Balance - (long)(creditAmount * 100) });
|
||||
|
||||
return !customerExists;
|
||||
}
|
||||
|
||||
@@ -630,7 +629,7 @@ public class StripePaymentService : IPaymentService
|
||||
return subscriptionInfo;
|
||||
}
|
||||
|
||||
var subscription = await _stripeAdapter.SubscriptionGetAsync(subscriber.GatewaySubscriptionId,
|
||||
var subscription = await _stripeAdapter.GetSubscriptionAsync(subscriber.GatewaySubscriptionId,
|
||||
new SubscriptionGetOptions { Expand = ["customer.discount.coupon.applies_to", "discounts.coupon.applies_to", "test_clock"] });
|
||||
|
||||
if (subscription == null)
|
||||
@@ -675,7 +674,7 @@ public class StripePaymentService : IPaymentService
|
||||
Subscription = subscriber.GatewaySubscriptionId
|
||||
};
|
||||
|
||||
var upcomingInvoice = await _stripeAdapter.InvoiceCreatePreviewAsync(invoiceCreatePreviewOptions);
|
||||
var upcomingInvoice = await _stripeAdapter.CreateInvoicePreviewAsync(invoiceCreatePreviewOptions);
|
||||
|
||||
if (upcomingInvoice != null)
|
||||
{
|
||||
@@ -726,7 +725,7 @@ public class StripePaymentService : IPaymentService
|
||||
return false;
|
||||
}
|
||||
|
||||
var customer = await _stripeAdapter.CustomerGetAsync(gatewayCustomerId);
|
||||
var customer = await _stripeAdapter.GetCustomerAsync(gatewayCustomerId);
|
||||
|
||||
return customer?.Discount?.Coupon?.Id == SecretsManagerStandaloneDiscountId;
|
||||
}
|
||||
@@ -738,7 +737,7 @@ public class StripePaymentService : IPaymentService
|
||||
return (null, null);
|
||||
}
|
||||
|
||||
var openInvoices = await _stripeAdapter.InvoiceSearchAsync(new InvoiceSearchOptions
|
||||
var openInvoices = await _stripeAdapter.SearchInvoiceAsync(new InvoiceSearchOptions
|
||||
{
|
||||
Query = $"subscription:'{subscription.Id}' status:'open'"
|
||||
});
|
||||
@@ -774,7 +773,7 @@ public class StripePaymentService : IPaymentService
|
||||
|
||||
private PaymentMethod GetLatestCardPaymentMethod(string customerId)
|
||||
{
|
||||
var cardPaymentMethods = _stripeAdapter.PaymentMethodListAutoPaging(
|
||||
var cardPaymentMethods = _stripeAdapter.ListPaymentMethodsAutoPaging(
|
||||
new PaymentMethodListOptions { Customer = customerId, Type = "card" });
|
||||
return cardPaymentMethods.OrderByDescending(m => m.Created).FirstOrDefault();
|
||||
}
|
||||
@@ -837,7 +836,7 @@ public class StripePaymentService : IPaymentService
|
||||
Customer customer = null;
|
||||
try
|
||||
{
|
||||
customer = await _stripeAdapter.CustomerGetAsync(gatewayCustomerId, options);
|
||||
customer = await _stripeAdapter.GetCustomerAsync(gatewayCustomerId, options);
|
||||
}
|
||||
catch (StripeException)
|
||||
{
|
||||
@@ -870,21 +869,21 @@ public class StripePaymentService : IPaymentService
|
||||
|
||||
try
|
||||
{
|
||||
var paidInvoicesTask = _stripeAdapter.InvoiceListAsync(new StripeInvoiceListOptions
|
||||
var paidInvoicesTask = _stripeAdapter.ListInvoicesAsync(new StripeInvoiceListOptions
|
||||
{
|
||||
Customer = customer.Id,
|
||||
SelectAll = !limit.HasValue,
|
||||
Limit = limit,
|
||||
Status = "paid"
|
||||
});
|
||||
var openInvoicesTask = _stripeAdapter.InvoiceListAsync(new StripeInvoiceListOptions
|
||||
var openInvoicesTask = _stripeAdapter.ListInvoicesAsync(new StripeInvoiceListOptions
|
||||
{
|
||||
Customer = customer.Id,
|
||||
SelectAll = !limit.HasValue,
|
||||
Limit = limit,
|
||||
Status = "open"
|
||||
});
|
||||
var uncollectibleInvoicesTask = _stripeAdapter.InvoiceListAsync(new StripeInvoiceListOptions
|
||||
var uncollectibleInvoicesTask = _stripeAdapter.ListInvoicesAsync(new StripeInvoiceListOptions
|
||||
{
|
||||
Customer = customer.Id,
|
||||
SelectAll = !limit.HasValue,
|
||||
@@ -1,6 +1,6 @@
|
||||
using Bit.Core.Exceptions;
|
||||
|
||||
namespace Bit.Core.Services;
|
||||
namespace Bit.Core.Billing.Services.Implementations;
|
||||
|
||||
public class StripeSyncService : IStripeSyncService
|
||||
{
|
||||
@@ -11,7 +11,7 @@ public class StripeSyncService : IStripeSyncService
|
||||
_stripeAdapter = stripeAdapter;
|
||||
}
|
||||
|
||||
public async Task UpdateCustomerEmailAddress(string gatewayCustomerId, string emailAddress)
|
||||
public async Task UpdateCustomerEmailAddressAsync(string gatewayCustomerId, string emailAddress)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(gatewayCustomerId))
|
||||
{
|
||||
@@ -23,9 +23,9 @@ public class StripeSyncService : IStripeSyncService
|
||||
throw new InvalidEmailException();
|
||||
}
|
||||
|
||||
var customer = await _stripeAdapter.CustomerGetAsync(gatewayCustomerId);
|
||||
var customer = await _stripeAdapter.GetCustomerAsync(gatewayCustomerId);
|
||||
|
||||
await _stripeAdapter.CustomerUpdateAsync(customer.Id,
|
||||
await _stripeAdapter.UpdateCustomerAsync(customer.Id,
|
||||
new Stripe.CustomerUpdateOptions { Email = emailAddress });
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,6 @@ using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Settings;
|
||||
using Bit.Core.Utilities;
|
||||
using Braintree;
|
||||
@@ -78,7 +77,7 @@ public class SubscriberService(
|
||||
{
|
||||
if (subscription.Metadata != null && subscription.Metadata.ContainsKey("organizationId"))
|
||||
{
|
||||
await stripeAdapter.SubscriptionUpdateAsync(subscription.Id, new SubscriptionUpdateOptions
|
||||
await stripeAdapter.UpdateSubscriptionAsync(subscription.Id, new SubscriptionUpdateOptions
|
||||
{
|
||||
Metadata = metadata
|
||||
});
|
||||
@@ -97,7 +96,7 @@ public class SubscriberService(
|
||||
options.CancellationDetails.Feedback = offboardingSurveyResponse.Reason;
|
||||
}
|
||||
|
||||
await stripeAdapter.SubscriptionCancelAsync(subscription.Id, options);
|
||||
await stripeAdapter.CancelSubscriptionAsync(subscription.Id, options);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -116,7 +115,7 @@ public class SubscriberService(
|
||||
options.CancellationDetails.Feedback = offboardingSurveyResponse.Reason;
|
||||
}
|
||||
|
||||
await stripeAdapter.SubscriptionUpdateAsync(subscription.Id, options);
|
||||
await stripeAdapter.UpdateSubscriptionAsync(subscription.Id, options);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -227,7 +226,7 @@ public class SubscriberService(
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(subscriber))
|
||||
};
|
||||
|
||||
var customer = await stripeAdapter.CustomerCreateAsync(options);
|
||||
var customer = await stripeAdapter.CreateCustomerAsync(options);
|
||||
|
||||
switch (subscriber)
|
||||
{
|
||||
@@ -270,7 +269,7 @@ public class SubscriberService(
|
||||
|
||||
try
|
||||
{
|
||||
var customer = await stripeAdapter.CustomerGetAsync(subscriber.GatewayCustomerId, customerGetOptions);
|
||||
var customer = await stripeAdapter.GetCustomerAsync(subscriber.GatewayCustomerId, customerGetOptions);
|
||||
|
||||
if (customer != null)
|
||||
{
|
||||
@@ -306,7 +305,7 @@ public class SubscriberService(
|
||||
|
||||
try
|
||||
{
|
||||
var customer = await stripeAdapter.CustomerGetAsync(subscriber.GatewayCustomerId, customerGetOptions);
|
||||
var customer = await stripeAdapter.GetCustomerAsync(subscriber.GatewayCustomerId, customerGetOptions);
|
||||
|
||||
if (customer != null)
|
||||
{
|
||||
@@ -357,7 +356,7 @@ public class SubscriberService(
|
||||
|
||||
try
|
||||
{
|
||||
var subscription = await stripeAdapter.SubscriptionGetAsync(subscriber.GatewaySubscriptionId, subscriptionGetOptions);
|
||||
var subscription = await stripeAdapter.GetSubscriptionAsync(subscriber.GatewaySubscriptionId, subscriptionGetOptions);
|
||||
|
||||
if (subscription != null)
|
||||
{
|
||||
@@ -393,7 +392,7 @@ public class SubscriberService(
|
||||
|
||||
try
|
||||
{
|
||||
var subscription = await stripeAdapter.SubscriptionGetAsync(subscriber.GatewaySubscriptionId, subscriptionGetOptions);
|
||||
var subscription = await stripeAdapter.GetSubscriptionAsync(subscriber.GatewaySubscriptionId, subscriptionGetOptions);
|
||||
|
||||
if (subscription != null)
|
||||
{
|
||||
@@ -487,23 +486,23 @@ public class SubscriberService(
|
||||
switch (source)
|
||||
{
|
||||
case BankAccount:
|
||||
await stripeAdapter.BankAccountDeleteAsync(stripeCustomer.Id, source.Id);
|
||||
await stripeAdapter.DeleteBankAccountAsync(stripeCustomer.Id, source.Id);
|
||||
break;
|
||||
case Card:
|
||||
await stripeAdapter.CardDeleteAsync(stripeCustomer.Id, source.Id);
|
||||
await stripeAdapter.DeleteCardAsync(stripeCustomer.Id, source.Id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var paymentMethods = stripeAdapter.PaymentMethodListAutoPagingAsync(new PaymentMethodListOptions
|
||||
var paymentMethods = stripeAdapter.ListPaymentMethodsAutoPagingAsync(new PaymentMethodListOptions
|
||||
{
|
||||
Customer = stripeCustomer.Id
|
||||
});
|
||||
|
||||
await foreach (var paymentMethod in paymentMethods)
|
||||
{
|
||||
await stripeAdapter.PaymentMethodDetachAsync(paymentMethod.Id);
|
||||
await stripeAdapter.DetachPaymentMethodAsync(paymentMethod.Id);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -532,7 +531,7 @@ public class SubscriberService(
|
||||
{
|
||||
case PaymentMethodType.BankAccount:
|
||||
{
|
||||
var getSetupIntentsForUpdatedPaymentMethod = stripeAdapter.SetupIntentList(new SetupIntentListOptions
|
||||
var getSetupIntentsForUpdatedPaymentMethod = stripeAdapter.ListSetupIntentsAsync(new SetupIntentListOptions
|
||||
{
|
||||
PaymentMethod = token
|
||||
});
|
||||
@@ -569,7 +568,7 @@ public class SubscriberService(
|
||||
await RemoveStripePaymentMethodsAsync(customer);
|
||||
|
||||
// Attach the incoming payment method.
|
||||
await stripeAdapter.PaymentMethodAttachAsync(token,
|
||||
await stripeAdapter.AttachPaymentMethodAsync(token,
|
||||
new PaymentMethodAttachOptions { Customer = subscriber.GatewayCustomerId });
|
||||
|
||||
var metadata = customer.Metadata;
|
||||
@@ -581,7 +580,7 @@ public class SubscriberService(
|
||||
}
|
||||
|
||||
// Set the customer's default payment method in Stripe and remove their Braintree customer ID.
|
||||
await stripeAdapter.CustomerUpdateAsync(subscriber.GatewayCustomerId, new CustomerUpdateOptions
|
||||
await stripeAdapter.UpdateCustomerAsync(subscriber.GatewayCustomerId, new CustomerUpdateOptions
|
||||
{
|
||||
InvoiceSettings = new CustomerInvoiceSettingsOptions
|
||||
{
|
||||
@@ -644,7 +643,7 @@ public class SubscriberService(
|
||||
Expand = ["subscriptions", "tax", "tax_ids"]
|
||||
});
|
||||
|
||||
customer = await stripeAdapter.CustomerUpdateAsync(customer.Id, new CustomerUpdateOptions
|
||||
customer = await stripeAdapter.UpdateCustomerAsync(customer.Id, new CustomerUpdateOptions
|
||||
{
|
||||
Address = new AddressOptions
|
||||
{
|
||||
@@ -662,7 +661,7 @@ public class SubscriberService(
|
||||
|
||||
if (taxId != null)
|
||||
{
|
||||
await stripeAdapter.TaxIdDeleteAsync(customer.Id, taxId.Id);
|
||||
await stripeAdapter.DeleteTaxIdAsync(customer.Id, taxId.Id);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(taxInformation.TaxId))
|
||||
@@ -685,12 +684,12 @@ public class SubscriberService(
|
||||
|
||||
try
|
||||
{
|
||||
await stripeAdapter.TaxIdCreateAsync(customer.Id,
|
||||
await stripeAdapter.CreateTaxIdAsync(customer.Id,
|
||||
new TaxIdCreateOptions { Type = taxIdType, Value = taxInformation.TaxId });
|
||||
|
||||
if (taxIdType == StripeConstants.TaxIdType.SpanishNIF)
|
||||
{
|
||||
await stripeAdapter.TaxIdCreateAsync(customer.Id,
|
||||
await stripeAdapter.CreateTaxIdAsync(customer.Id,
|
||||
new TaxIdCreateOptions { Type = StripeConstants.TaxIdType.EUVAT, Value = $"ES{taxInformation.TaxId}" });
|
||||
}
|
||||
}
|
||||
@@ -736,7 +735,7 @@ public class SubscriberService(
|
||||
Address.Country: not Core.Constants.CountryAbbreviations.UnitedStates,
|
||||
TaxExempt: not TaxExempt.Reverse
|
||||
}:
|
||||
await stripeAdapter.CustomerUpdateAsync(customer.Id,
|
||||
await stripeAdapter.UpdateCustomerAsync(customer.Id,
|
||||
new CustomerUpdateOptions { TaxExempt = TaxExempt.Reverse });
|
||||
break;
|
||||
case
|
||||
@@ -744,14 +743,14 @@ public class SubscriberService(
|
||||
Address.Country: Core.Constants.CountryAbbreviations.UnitedStates,
|
||||
TaxExempt: TaxExempt.Reverse
|
||||
}:
|
||||
await stripeAdapter.CustomerUpdateAsync(customer.Id,
|
||||
await stripeAdapter.UpdateCustomerAsync(customer.Id,
|
||||
new CustomerUpdateOptions { TaxExempt = TaxExempt.None });
|
||||
break;
|
||||
}
|
||||
|
||||
if (!subscription.AutomaticTax.Enabled)
|
||||
{
|
||||
await stripeAdapter.SubscriptionUpdateAsync(subscription.Id,
|
||||
await stripeAdapter.UpdateSubscriptionAsync(subscription.Id,
|
||||
new SubscriptionUpdateOptions
|
||||
{
|
||||
AutomaticTax = new SubscriptionAutomaticTaxOptions { Enabled = true }
|
||||
@@ -771,7 +770,7 @@ public class SubscriberService(
|
||||
|
||||
if (automaticTaxShouldBeEnabled && !subscription.AutomaticTax.Enabled)
|
||||
{
|
||||
await stripeAdapter.SubscriptionUpdateAsync(subscription.Id,
|
||||
await stripeAdapter.UpdateSubscriptionAsync(subscription.Id,
|
||||
new SubscriptionUpdateOptions
|
||||
{
|
||||
AutomaticTax = new SubscriptionAutomaticTaxOptions { Enabled = true }
|
||||
@@ -790,7 +789,7 @@ public class SubscriberService(
|
||||
}
|
||||
try
|
||||
{
|
||||
await stripeAdapter.CustomerGetAsync(subscriber.GatewayCustomerId);
|
||||
await stripeAdapter.GetCustomerAsync(subscriber.GatewayCustomerId);
|
||||
return true;
|
||||
}
|
||||
catch (StripeException e) when (e.StripeError.Code == "resource_missing")
|
||||
@@ -809,7 +808,7 @@ public class SubscriberService(
|
||||
}
|
||||
try
|
||||
{
|
||||
await stripeAdapter.SubscriptionGetAsync(subscriber.GatewaySubscriptionId);
|
||||
await stripeAdapter.GetSubscriptionAsync(subscriber.GatewaySubscriptionId);
|
||||
return true;
|
||||
}
|
||||
catch (StripeException e) when (e.StripeError.Code == "resource_missing")
|
||||
@@ -828,7 +827,7 @@ public class SubscriberService(
|
||||
|
||||
metadata[BraintreeCustomerIdKey] = braintreeCustomerId;
|
||||
|
||||
await stripeAdapter.CustomerUpdateAsync(customer.Id, new CustomerUpdateOptions
|
||||
await stripeAdapter.UpdateCustomerAsync(customer.Id, new CustomerUpdateOptions
|
||||
{
|
||||
Metadata = metadata
|
||||
});
|
||||
@@ -868,7 +867,7 @@ public class SubscriberService(
|
||||
return null;
|
||||
}
|
||||
|
||||
var setupIntent = await stripeAdapter.SetupIntentGet(setupIntentId, new SetupIntentGetOptions
|
||||
var setupIntent = await stripeAdapter.GetSetupIntentAsync(setupIntentId, new SetupIntentGetOptions
|
||||
{
|
||||
Expand = ["payment_method"]
|
||||
});
|
||||
@@ -886,7 +885,7 @@ public class SubscriberService(
|
||||
metadata[BraintreeCustomerIdOldKey] = value;
|
||||
metadata[BraintreeCustomerIdKey] = null;
|
||||
|
||||
await stripeAdapter.CustomerUpdateAsync(customer.Id, new CustomerUpdateOptions
|
||||
await stripeAdapter.UpdateCustomerAsync(customer.Id, new CustomerUpdateOptions
|
||||
{
|
||||
Metadata = metadata
|
||||
});
|
||||
@@ -903,18 +902,18 @@ public class SubscriberService(
|
||||
switch (source)
|
||||
{
|
||||
case BankAccount:
|
||||
await stripeAdapter.BankAccountDeleteAsync(customer.Id, source.Id);
|
||||
await stripeAdapter.DeleteBankAccountAsync(customer.Id, source.Id);
|
||||
break;
|
||||
case Card:
|
||||
await stripeAdapter.CardDeleteAsync(customer.Id, source.Id);
|
||||
await stripeAdapter.DeleteCardAsync(customer.Id, source.Id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var paymentMethods = await stripeAdapter.CustomerListPaymentMethods(customer.Id);
|
||||
var paymentMethods = await stripeAdapter.ListCustomerPaymentMethodsAsync(customer.Id);
|
||||
|
||||
await Task.WhenAll(paymentMethods.Select(pm => stripeAdapter.PaymentMethodDetachAsync(pm.Id)));
|
||||
await Task.WhenAll(paymentMethods.Select(pm => stripeAdapter.DetachPaymentMethodAsync(pm.Id)));
|
||||
}
|
||||
|
||||
private async Task ReplaceBraintreePaymentMethodAsync(
|
||||
|
||||
@@ -7,7 +7,6 @@ using Bit.Core.Billing.Extensions;
|
||||
using Bit.Core.Billing.Services;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using OneOf.Types;
|
||||
using Stripe;
|
||||
|
||||
@@ -53,7 +52,7 @@ public class RestartSubscriptionCommand(
|
||||
TrialPeriodDays = 0
|
||||
};
|
||||
|
||||
var subscription = await stripeAdapter.SubscriptionCreateAsync(options);
|
||||
var subscription = await stripeAdapter.CreateSubscriptionAsync(options);
|
||||
await EnableAsync(subscriber, subscription);
|
||||
return new None();
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
#nullable disable
|
||||
|
||||
using Bit.Core.Billing.Models;
|
||||
using Bit.Core.Billing.Services;
|
||||
using Bit.Core.Billing.Tax.Models;
|
||||
using Bit.Core.Services;
|
||||
using Stripe;
|
||||
|
||||
namespace Bit.Core.Billing;
|
||||
@@ -22,7 +22,7 @@ public static class Utilities
|
||||
return null;
|
||||
}
|
||||
|
||||
var openInvoices = await stripeAdapter.InvoiceSearchAsync(new InvoiceSearchOptions
|
||||
var openInvoices = await stripeAdapter.SearchInvoiceAsync(new InvoiceSearchOptions
|
||||
{
|
||||
Query = $"subscription:'{subscription.Id}' status:'open'"
|
||||
});
|
||||
|
||||
@@ -160,7 +160,6 @@ public static class FeatureFlagKeys
|
||||
public const string DisableAlternateLoginMethods = "pm-22110-disable-alternate-login-methods";
|
||||
public const string PM23174ManageAccountRecoveryPermissionDrivesTheNeedToSetMasterPassword =
|
||||
"pm-23174-manage-account-recovery-permission-drives-the-need-to-set-master-password";
|
||||
public const string RecoveryCodeSupportForSsoRequiredUsers = "pm-21153-recovery-code-support-for-sso-required";
|
||||
public const string MJMLBasedEmailTemplates = "mjml-based-email-templates";
|
||||
public const string MjmlWelcomeEmailTemplates = "pm-21741-mjml-welcome-email";
|
||||
public const string MarketingInitiatedPremiumFlow = "pm-26140-marketing-initiated-premium-flow";
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
<PackageReference Include="Microsoft.Azure.Cosmos" Version="3.52.0" />
|
||||
<PackageReference Include="Microsoft.Azure.NotificationHubs" Version="4.2.0" />
|
||||
<PackageReference Include="Microsoft.Bot.Builder" Version="4.23.0" />
|
||||
<PackageReference Include="Microsoft.Bot.Builder.Integration.AspNet.Core" Version="4.23.0" />
|
||||
<PackageReference Include="Microsoft.Bot.Connector" Version="4.23.0" />
|
||||
<PackageReference Include="Microsoft.Data.SqlClient" Version="5.2.2" />
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.Cosmos" Version="1.7.0" />
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.Billing.Extensions;
|
||||
using Bit.Core.Billing.Models;
|
||||
using Bit.Core.Billing.Services;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Interfaces;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
|
||||
namespace Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Cloud;
|
||||
|
||||
@@ -13,9 +13,9 @@ public class SetUpSponsorshipCommand : ISetUpSponsorshipCommand
|
||||
{
|
||||
private readonly IOrganizationSponsorshipRepository _organizationSponsorshipRepository;
|
||||
private readonly IOrganizationRepository _organizationRepository;
|
||||
private readonly IPaymentService _paymentService;
|
||||
private readonly IStripePaymentService _paymentService;
|
||||
|
||||
public SetUpSponsorshipCommand(IOrganizationSponsorshipRepository organizationSponsorshipRepository, IOrganizationRepository organizationRepository, IPaymentService paymentService)
|
||||
public SetUpSponsorshipCommand(IOrganizationSponsorshipRepository organizationSponsorshipRepository, IOrganizationRepository organizationRepository, IStripePaymentService paymentService)
|
||||
{
|
||||
_organizationSponsorshipRepository = organizationSponsorshipRepository;
|
||||
_organizationRepository = organizationRepository;
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.Billing.Extensions;
|
||||
using Bit.Core.Billing.Models;
|
||||
using Bit.Core.Billing.Services;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Interfaces;
|
||||
using Bit.Core.Repositories;
|
||||
@@ -14,14 +15,14 @@ namespace Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnte
|
||||
|
||||
public class ValidateSponsorshipCommand : CancelSponsorshipCommand, IValidateSponsorshipCommand
|
||||
{
|
||||
private readonly IPaymentService _paymentService;
|
||||
private readonly IStripePaymentService _paymentService;
|
||||
private readonly IMailService _mailService;
|
||||
private readonly ILogger<ValidateSponsorshipCommand> _logger;
|
||||
|
||||
public ValidateSponsorshipCommand(
|
||||
IOrganizationSponsorshipRepository organizationSponsorshipRepository,
|
||||
IOrganizationRepository organizationRepository,
|
||||
IPaymentService paymentService,
|
||||
IStripePaymentService paymentService,
|
||||
IMailService mailService,
|
||||
ILogger<ValidateSponsorshipCommand> logger) : base(organizationSponsorshipRepository, organizationRepository)
|
||||
{
|
||||
|
||||
@@ -3,6 +3,7 @@ using Bit.Core.AdminConsole.Enums.Provider;
|
||||
using Bit.Core.AdminConsole.Repositories;
|
||||
using Bit.Core.Billing.Enums;
|
||||
using Bit.Core.Billing.Pricing;
|
||||
using Bit.Core.Billing.Services;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Models.Business;
|
||||
using Bit.Core.OrganizationFeatures.OrganizationSubscriptions.Interface;
|
||||
@@ -12,13 +13,13 @@ namespace Bit.Core.OrganizationFeatures.OrganizationSubscriptions;
|
||||
|
||||
public class AddSecretsManagerSubscriptionCommand : IAddSecretsManagerSubscriptionCommand
|
||||
{
|
||||
private readonly IPaymentService _paymentService;
|
||||
private readonly IStripePaymentService _paymentService;
|
||||
private readonly IOrganizationService _organizationService;
|
||||
private readonly IProviderRepository _providerRepository;
|
||||
private readonly IPricingClient _pricingClient;
|
||||
|
||||
public AddSecretsManagerSubscriptionCommand(
|
||||
IPaymentService paymentService,
|
||||
IStripePaymentService paymentService,
|
||||
IOrganizationService organizationService,
|
||||
IProviderRepository providerRepository,
|
||||
IPricingClient pricingClient)
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.Billing.Enums;
|
||||
using Bit.Core.Billing.Services;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Models.Business;
|
||||
@@ -18,7 +19,7 @@ namespace Bit.Core.OrganizationFeatures.OrganizationSubscriptions;
|
||||
public class UpdateSecretsManagerSubscriptionCommand : IUpdateSecretsManagerSubscriptionCommand
|
||||
{
|
||||
private readonly IOrganizationUserRepository _organizationUserRepository;
|
||||
private readonly IPaymentService _paymentService;
|
||||
private readonly IStripePaymentService _paymentService;
|
||||
private readonly IMailService _mailService;
|
||||
private readonly ILogger<UpdateSecretsManagerSubscriptionCommand> _logger;
|
||||
private readonly IServiceAccountRepository _serviceAccountRepository;
|
||||
@@ -29,7 +30,7 @@ public class UpdateSecretsManagerSubscriptionCommand : IUpdateSecretsManagerSubs
|
||||
|
||||
public UpdateSecretsManagerSubscriptionCommand(
|
||||
IOrganizationUserRepository organizationUserRepository,
|
||||
IPaymentService paymentService,
|
||||
IStripePaymentService paymentService,
|
||||
IMailService mailService,
|
||||
ILogger<UpdateSecretsManagerSubscriptionCommand> logger,
|
||||
IServiceAccountRepository serviceAccountRepository,
|
||||
|
||||
@@ -11,6 +11,7 @@ using Bit.Core.Billing.Enums;
|
||||
using Bit.Core.Billing.Organizations.Models;
|
||||
using Bit.Core.Billing.Organizations.Services;
|
||||
using Bit.Core.Billing.Pricing;
|
||||
using Bit.Core.Billing.Services;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Models.Business;
|
||||
@@ -26,7 +27,7 @@ public class UpgradeOrganizationPlanCommand : IUpgradeOrganizationPlanCommand
|
||||
private readonly IOrganizationUserRepository _organizationUserRepository;
|
||||
private readonly ICollectionRepository _collectionRepository;
|
||||
private readonly IGroupRepository _groupRepository;
|
||||
private readonly IPaymentService _paymentService;
|
||||
private readonly IStripePaymentService _paymentService;
|
||||
private readonly IPolicyRepository _policyRepository;
|
||||
private readonly ISsoConfigRepository _ssoConfigRepository;
|
||||
private readonly IOrganizationConnectionRepository _organizationConnectionRepository;
|
||||
@@ -41,7 +42,7 @@ public class UpgradeOrganizationPlanCommand : IUpgradeOrganizationPlanCommand
|
||||
IOrganizationUserRepository organizationUserRepository,
|
||||
ICollectionRepository collectionRepository,
|
||||
IGroupRepository groupRepository,
|
||||
IPaymentService paymentService,
|
||||
IStripePaymentService paymentService,
|
||||
IPolicyRepository policyRepository,
|
||||
ISsoConfigRepository ssoConfigRepository,
|
||||
IOrganizationConnectionRepository organizationConnectionRepository,
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
// FIXME: Update this file to be null safe and then delete the line below
|
||||
#nullable disable
|
||||
|
||||
using Bit.Core.Models.BitStripe;
|
||||
using Stripe;
|
||||
using Stripe.Tax;
|
||||
|
||||
namespace Bit.Core.Services;
|
||||
|
||||
public interface IStripeAdapter
|
||||
{
|
||||
Task<Customer> CustomerCreateAsync(CustomerCreateOptions customerCreateOptions);
|
||||
Task CustomerDeleteDiscountAsync(string customerId, CustomerDeleteDiscountOptions options = null);
|
||||
Task<Customer> CustomerGetAsync(string id, CustomerGetOptions options = null);
|
||||
Task<Customer> CustomerUpdateAsync(string id, CustomerUpdateOptions options = null);
|
||||
Task<Customer> CustomerDeleteAsync(string id);
|
||||
Task<List<PaymentMethod>> CustomerListPaymentMethods(string id, CustomerPaymentMethodListOptions options = null);
|
||||
Task<CustomerBalanceTransaction> CustomerBalanceTransactionCreate(string customerId,
|
||||
CustomerBalanceTransactionCreateOptions options);
|
||||
Task<Subscription> SubscriptionCreateAsync(SubscriptionCreateOptions subscriptionCreateOptions);
|
||||
Task<Subscription> SubscriptionGetAsync(string id, SubscriptionGetOptions options = null);
|
||||
Task<Subscription> SubscriptionUpdateAsync(string id, SubscriptionUpdateOptions options = null);
|
||||
Task<Subscription> SubscriptionCancelAsync(string Id, SubscriptionCancelOptions options = null);
|
||||
Task<Invoice> InvoiceGetAsync(string id, InvoiceGetOptions options);
|
||||
Task<List<Invoice>> InvoiceListAsync(StripeInvoiceListOptions options);
|
||||
Task<Invoice> InvoiceCreatePreviewAsync(InvoiceCreatePreviewOptions options);
|
||||
Task<List<Invoice>> InvoiceSearchAsync(InvoiceSearchOptions options);
|
||||
Task<Invoice> InvoiceUpdateAsync(string id, InvoiceUpdateOptions options);
|
||||
Task<Invoice> InvoiceFinalizeInvoiceAsync(string id, InvoiceFinalizeOptions options);
|
||||
Task<Invoice> InvoiceSendInvoiceAsync(string id, InvoiceSendOptions options);
|
||||
Task<Invoice> InvoicePayAsync(string id, InvoicePayOptions options = null);
|
||||
Task<Invoice> InvoiceDeleteAsync(string id, InvoiceDeleteOptions options = null);
|
||||
Task<Invoice> InvoiceVoidInvoiceAsync(string id, InvoiceVoidOptions options = null);
|
||||
IEnumerable<PaymentMethod> PaymentMethodListAutoPaging(PaymentMethodListOptions options);
|
||||
IAsyncEnumerable<PaymentMethod> PaymentMethodListAutoPagingAsync(PaymentMethodListOptions options);
|
||||
Task<PaymentMethod> PaymentMethodAttachAsync(string id, PaymentMethodAttachOptions options = null);
|
||||
Task<PaymentMethod> PaymentMethodDetachAsync(string id, PaymentMethodDetachOptions options = null);
|
||||
Task<TaxId> TaxIdCreateAsync(string id, TaxIdCreateOptions options);
|
||||
Task<TaxId> TaxIdDeleteAsync(string customerId, string taxIdId, TaxIdDeleteOptions options = null);
|
||||
Task<StripeList<Registration>> TaxRegistrationsListAsync(RegistrationListOptions options = null);
|
||||
Task<StripeList<Charge>> ChargeListAsync(ChargeListOptions options);
|
||||
Task<Refund> RefundCreateAsync(RefundCreateOptions options);
|
||||
Task<Card> CardDeleteAsync(string customerId, string cardId, CardDeleteOptions options = null);
|
||||
Task<BankAccount> BankAccountCreateAsync(string customerId, BankAccountCreateOptions options = null);
|
||||
Task<BankAccount> BankAccountDeleteAsync(string customerId, string bankAccount, BankAccountDeleteOptions options = null);
|
||||
Task<StripeList<Price>> PriceListAsync(PriceListOptions options = null);
|
||||
Task<SetupIntent> SetupIntentCreate(SetupIntentCreateOptions options);
|
||||
Task<List<SetupIntent>> SetupIntentList(SetupIntentListOptions options);
|
||||
Task SetupIntentCancel(string id, SetupIntentCancelOptions options = null);
|
||||
Task<SetupIntent> SetupIntentGet(string id, SetupIntentGetOptions options = null);
|
||||
Task SetupIntentVerifyMicroDeposit(string id, SetupIntentVerifyMicrodepositsOptions options);
|
||||
Task<List<Stripe.TestHelpers.TestClock>> TestClockListAsync();
|
||||
Task<Price> PriceGetAsync(string id, PriceGetOptions options = null);
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
namespace Bit.Core.Services;
|
||||
|
||||
public interface IStripeSyncService
|
||||
{
|
||||
Task UpdateCustomerEmailAddress(string gatewayCustomerId, string emailAddress);
|
||||
}
|
||||
@@ -1,284 +0,0 @@
|
||||
// FIXME: Update this file to be null safe and then delete the line below
|
||||
#nullable disable
|
||||
|
||||
using Bit.Core.Models.BitStripe;
|
||||
using Stripe;
|
||||
using Stripe.Tax;
|
||||
|
||||
namespace Bit.Core.Services;
|
||||
|
||||
public class StripeAdapter : IStripeAdapter
|
||||
{
|
||||
private readonly CustomerService _customerService;
|
||||
private readonly SubscriptionService _subscriptionService;
|
||||
private readonly InvoiceService _invoiceService;
|
||||
private readonly PaymentMethodService _paymentMethodService;
|
||||
private readonly TaxIdService _taxIdService;
|
||||
private readonly ChargeService _chargeService;
|
||||
private readonly RefundService _refundService;
|
||||
private readonly CardService _cardService;
|
||||
private readonly BankAccountService _bankAccountService;
|
||||
private readonly PlanService _planService;
|
||||
private readonly PriceService _priceService;
|
||||
private readonly SetupIntentService _setupIntentService;
|
||||
private readonly Stripe.TestHelpers.TestClockService _testClockService;
|
||||
private readonly CustomerBalanceTransactionService _customerBalanceTransactionService;
|
||||
private readonly Stripe.Tax.RegistrationService _taxRegistrationService;
|
||||
private readonly CalculationService _calculationService;
|
||||
|
||||
public StripeAdapter()
|
||||
{
|
||||
_customerService = new CustomerService();
|
||||
_subscriptionService = new SubscriptionService();
|
||||
_invoiceService = new InvoiceService();
|
||||
_paymentMethodService = new PaymentMethodService();
|
||||
_taxIdService = new TaxIdService();
|
||||
_chargeService = new ChargeService();
|
||||
_refundService = new RefundService();
|
||||
_cardService = new CardService();
|
||||
_bankAccountService = new BankAccountService();
|
||||
_priceService = new PriceService();
|
||||
_planService = new PlanService();
|
||||
_setupIntentService = new SetupIntentService();
|
||||
_testClockService = new Stripe.TestHelpers.TestClockService();
|
||||
_customerBalanceTransactionService = new CustomerBalanceTransactionService();
|
||||
_taxRegistrationService = new Stripe.Tax.RegistrationService();
|
||||
_calculationService = new CalculationService();
|
||||
}
|
||||
|
||||
public Task<Customer> CustomerCreateAsync(CustomerCreateOptions options)
|
||||
{
|
||||
return _customerService.CreateAsync(options);
|
||||
}
|
||||
|
||||
public Task CustomerDeleteDiscountAsync(string customerId, CustomerDeleteDiscountOptions options = null) =>
|
||||
_customerService.DeleteDiscountAsync(customerId, options);
|
||||
|
||||
public Task<Customer> CustomerGetAsync(string id, CustomerGetOptions options = null)
|
||||
{
|
||||
return _customerService.GetAsync(id, options);
|
||||
}
|
||||
|
||||
public Task<Customer> CustomerUpdateAsync(string id, CustomerUpdateOptions options = null)
|
||||
{
|
||||
return _customerService.UpdateAsync(id, options);
|
||||
}
|
||||
|
||||
public Task<Customer> CustomerDeleteAsync(string id)
|
||||
{
|
||||
return _customerService.DeleteAsync(id);
|
||||
}
|
||||
|
||||
public async Task<List<PaymentMethod>> CustomerListPaymentMethods(string id,
|
||||
CustomerPaymentMethodListOptions options = null)
|
||||
{
|
||||
var paymentMethods = await _customerService.ListPaymentMethodsAsync(id, options);
|
||||
return paymentMethods.Data;
|
||||
}
|
||||
|
||||
public async Task<CustomerBalanceTransaction> CustomerBalanceTransactionCreate(string customerId,
|
||||
CustomerBalanceTransactionCreateOptions options)
|
||||
=> await _customerBalanceTransactionService.CreateAsync(customerId, options);
|
||||
|
||||
public Task<Subscription> SubscriptionCreateAsync(SubscriptionCreateOptions options)
|
||||
{
|
||||
return _subscriptionService.CreateAsync(options);
|
||||
}
|
||||
|
||||
public Task<Subscription> SubscriptionGetAsync(string id, SubscriptionGetOptions options = null)
|
||||
{
|
||||
return _subscriptionService.GetAsync(id, options);
|
||||
}
|
||||
|
||||
public async Task<Subscription> ProviderSubscriptionGetAsync(
|
||||
string id,
|
||||
Guid providerId,
|
||||
SubscriptionGetOptions options = null)
|
||||
{
|
||||
var subscription = await _subscriptionService.GetAsync(id, options);
|
||||
if (subscription.Metadata.TryGetValue("providerId", out var value) && value == providerId.ToString())
|
||||
{
|
||||
return subscription;
|
||||
}
|
||||
|
||||
throw new InvalidOperationException("Subscription does not belong to the provider.");
|
||||
}
|
||||
|
||||
public Task<Subscription> SubscriptionUpdateAsync(string id,
|
||||
SubscriptionUpdateOptions options = null)
|
||||
{
|
||||
return _subscriptionService.UpdateAsync(id, options);
|
||||
}
|
||||
|
||||
public Task<Subscription> SubscriptionCancelAsync(string Id, SubscriptionCancelOptions options = null)
|
||||
{
|
||||
return _subscriptionService.CancelAsync(Id, options);
|
||||
}
|
||||
|
||||
public Task<Invoice> InvoiceGetAsync(string id, InvoiceGetOptions options)
|
||||
{
|
||||
return _invoiceService.GetAsync(id, options);
|
||||
}
|
||||
|
||||
public async Task<List<Invoice>> InvoiceListAsync(StripeInvoiceListOptions options)
|
||||
{
|
||||
if (!options.SelectAll)
|
||||
{
|
||||
return (await _invoiceService.ListAsync(options.ToInvoiceListOptions())).Data;
|
||||
}
|
||||
|
||||
options.Limit = 100;
|
||||
|
||||
var invoices = new List<Invoice>();
|
||||
|
||||
await foreach (var invoice in _invoiceService.ListAutoPagingAsync(options.ToInvoiceListOptions()))
|
||||
{
|
||||
invoices.Add(invoice);
|
||||
}
|
||||
|
||||
return invoices;
|
||||
}
|
||||
|
||||
public Task<Invoice> InvoiceCreatePreviewAsync(InvoiceCreatePreviewOptions options)
|
||||
{
|
||||
return _invoiceService.CreatePreviewAsync(options);
|
||||
}
|
||||
|
||||
public async Task<List<Invoice>> InvoiceSearchAsync(InvoiceSearchOptions options)
|
||||
=> (await _invoiceService.SearchAsync(options)).Data;
|
||||
|
||||
public Task<Invoice> InvoiceUpdateAsync(string id, InvoiceUpdateOptions options)
|
||||
{
|
||||
return _invoiceService.UpdateAsync(id, options);
|
||||
}
|
||||
|
||||
public Task<Invoice> InvoiceFinalizeInvoiceAsync(string id, InvoiceFinalizeOptions options)
|
||||
{
|
||||
return _invoiceService.FinalizeInvoiceAsync(id, options);
|
||||
}
|
||||
|
||||
public Task<Invoice> InvoiceSendInvoiceAsync(string id, InvoiceSendOptions options)
|
||||
{
|
||||
return _invoiceService.SendInvoiceAsync(id, options);
|
||||
}
|
||||
|
||||
public Task<Invoice> InvoicePayAsync(string id, InvoicePayOptions options = null)
|
||||
{
|
||||
return _invoiceService.PayAsync(id, options);
|
||||
}
|
||||
|
||||
public Task<Invoice> InvoiceDeleteAsync(string id, InvoiceDeleteOptions options = null)
|
||||
{
|
||||
return _invoiceService.DeleteAsync(id, options);
|
||||
}
|
||||
|
||||
public Task<Invoice> InvoiceVoidInvoiceAsync(string id, InvoiceVoidOptions options = null)
|
||||
{
|
||||
return _invoiceService.VoidInvoiceAsync(id, options);
|
||||
}
|
||||
|
||||
public IEnumerable<PaymentMethod> PaymentMethodListAutoPaging(PaymentMethodListOptions options)
|
||||
{
|
||||
return _paymentMethodService.ListAutoPaging(options);
|
||||
}
|
||||
|
||||
public IAsyncEnumerable<PaymentMethod> PaymentMethodListAutoPagingAsync(PaymentMethodListOptions options)
|
||||
=> _paymentMethodService.ListAutoPagingAsync(options);
|
||||
|
||||
public Task<PaymentMethod> PaymentMethodAttachAsync(string id, PaymentMethodAttachOptions options = null)
|
||||
{
|
||||
return _paymentMethodService.AttachAsync(id, options);
|
||||
}
|
||||
|
||||
public Task<PaymentMethod> PaymentMethodDetachAsync(string id, PaymentMethodDetachOptions options = null)
|
||||
{
|
||||
return _paymentMethodService.DetachAsync(id, options);
|
||||
}
|
||||
|
||||
public Task<Plan> PlanGetAsync(string id, PlanGetOptions options = null)
|
||||
{
|
||||
return _planService.GetAsync(id, options);
|
||||
}
|
||||
|
||||
public Task<TaxId> TaxIdCreateAsync(string id, TaxIdCreateOptions options)
|
||||
{
|
||||
return _taxIdService.CreateAsync(id, options);
|
||||
}
|
||||
|
||||
public Task<TaxId> TaxIdDeleteAsync(string customerId, string taxIdId,
|
||||
TaxIdDeleteOptions options = null)
|
||||
{
|
||||
return _taxIdService.DeleteAsync(customerId, taxIdId);
|
||||
}
|
||||
|
||||
public Task<StripeList<Registration>> TaxRegistrationsListAsync(RegistrationListOptions options = null)
|
||||
{
|
||||
return _taxRegistrationService.ListAsync(options);
|
||||
}
|
||||
|
||||
public Task<StripeList<Charge>> ChargeListAsync(ChargeListOptions options)
|
||||
{
|
||||
return _chargeService.ListAsync(options);
|
||||
}
|
||||
|
||||
public Task<Refund> RefundCreateAsync(RefundCreateOptions options)
|
||||
{
|
||||
return _refundService.CreateAsync(options);
|
||||
}
|
||||
|
||||
public Task<Card> CardDeleteAsync(string customerId, string cardId, CardDeleteOptions options = null)
|
||||
{
|
||||
return _cardService.DeleteAsync(customerId, cardId, options);
|
||||
}
|
||||
|
||||
public Task<BankAccount> BankAccountCreateAsync(string customerId, BankAccountCreateOptions options = null)
|
||||
{
|
||||
return _bankAccountService.CreateAsync(customerId, options);
|
||||
}
|
||||
|
||||
public Task<BankAccount> BankAccountDeleteAsync(string customerId, string bankAccount, BankAccountDeleteOptions options = null)
|
||||
{
|
||||
return _bankAccountService.DeleteAsync(customerId, bankAccount, options);
|
||||
}
|
||||
|
||||
public async Task<StripeList<Price>> PriceListAsync(PriceListOptions options = null)
|
||||
{
|
||||
return await _priceService.ListAsync(options);
|
||||
}
|
||||
|
||||
public Task<SetupIntent> SetupIntentCreate(SetupIntentCreateOptions options)
|
||||
=> _setupIntentService.CreateAsync(options);
|
||||
|
||||
public async Task<List<SetupIntent>> SetupIntentList(SetupIntentListOptions options)
|
||||
{
|
||||
var setupIntents = await _setupIntentService.ListAsync(options);
|
||||
|
||||
return setupIntents.Data;
|
||||
}
|
||||
|
||||
public Task SetupIntentCancel(string id, SetupIntentCancelOptions options = null)
|
||||
=> _setupIntentService.CancelAsync(id, options);
|
||||
|
||||
public Task<SetupIntent> SetupIntentGet(string id, SetupIntentGetOptions options = null)
|
||||
=> _setupIntentService.GetAsync(id, options);
|
||||
|
||||
public Task SetupIntentVerifyMicroDeposit(string id, SetupIntentVerifyMicrodepositsOptions options)
|
||||
=> _setupIntentService.VerifyMicrodepositsAsync(id, options);
|
||||
|
||||
public async Task<List<Stripe.TestHelpers.TestClock>> TestClockListAsync()
|
||||
{
|
||||
var items = new List<Stripe.TestHelpers.TestClock>();
|
||||
var options = new Stripe.TestHelpers.TestClockListOptions()
|
||||
{
|
||||
Limit = 100
|
||||
};
|
||||
await foreach (var i in _testClockService.ListAutoPagingAsync(options))
|
||||
{
|
||||
items.Add(i);
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
public Task<Price> PriceGetAsync(string id, PriceGetOptions options = null)
|
||||
=> _priceService.GetAsync(id, options);
|
||||
}
|
||||
@@ -57,7 +57,7 @@ public class UserService : UserManager<User>, IUserService
|
||||
private readonly ILicensingService _licenseService;
|
||||
private readonly IEventService _eventService;
|
||||
private readonly IApplicationCacheService _applicationCacheService;
|
||||
private readonly IPaymentService _paymentService;
|
||||
private readonly IStripePaymentService _paymentService;
|
||||
private readonly IPolicyRepository _policyRepository;
|
||||
private readonly IPolicyService _policyService;
|
||||
private readonly IFido2 _fido2;
|
||||
@@ -93,7 +93,7 @@ public class UserService : UserManager<User>, IUserService
|
||||
ILicensingService licenseService,
|
||||
IEventService eventService,
|
||||
IApplicationCacheService applicationCacheService,
|
||||
IPaymentService paymentService,
|
||||
IStripePaymentService paymentService,
|
||||
IPolicyRepository policyRepository,
|
||||
IPolicyService policyService,
|
||||
IFido2 fido2,
|
||||
@@ -534,7 +534,7 @@ public class UserService : UserManager<User>, IUserService
|
||||
|
||||
try
|
||||
{
|
||||
await _stripeSyncService.UpdateCustomerEmailAddress(user.GatewayCustomerId,
|
||||
await _stripeSyncService.UpdateCustomerEmailAddressAsync(user.GatewayCustomerId,
|
||||
user.BillingEmailAddress());
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -867,7 +867,7 @@ public class UserService : UserManager<User>, IUserService
|
||||
}
|
||||
|
||||
string paymentIntentClientSecret = null;
|
||||
IPaymentService paymentService = null;
|
||||
IStripePaymentService paymentService = null;
|
||||
if (_globalSettings.SelfHosted)
|
||||
{
|
||||
if (license == null || !_licenseService.VerifyLicense(license))
|
||||
|
||||
@@ -56,7 +56,7 @@ public class GlobalSettings : IGlobalSettings
|
||||
public virtual EventLoggingSettings EventLogging { get; set; } = new EventLoggingSettings();
|
||||
public virtual MailSettings Mail { get; set; } = new MailSettings();
|
||||
public virtual IConnectionStringSettings Storage { get; set; } = new ConnectionStringSettings();
|
||||
public virtual ConnectionStringSettings Events { get; set; } = new ConnectionStringSettings();
|
||||
public virtual AzureQueueEventSettings Events { get; set; } = new AzureQueueEventSettings();
|
||||
public virtual DistributedCacheSettings DistributedCache { get; set; } = new DistributedCacheSettings();
|
||||
public virtual NotificationsSettings Notifications { get; set; } = new NotificationsSettings();
|
||||
public virtual IFileStorageSettings Attachment { get; set; }
|
||||
@@ -395,6 +395,24 @@ public class GlobalSettings : IGlobalSettings
|
||||
}
|
||||
}
|
||||
|
||||
public class AzureQueueEventSettings : IConnectionStringSettings
|
||||
{
|
||||
private string _connectionString;
|
||||
private string _queueName;
|
||||
|
||||
public string ConnectionString
|
||||
{
|
||||
get => _connectionString;
|
||||
set => _connectionString = value?.Trim('"');
|
||||
}
|
||||
|
||||
public string QueueName
|
||||
{
|
||||
get => _queueName;
|
||||
set => _queueName = value?.Trim('"');
|
||||
}
|
||||
}
|
||||
|
||||
public class ConnectionStringSettings : IConnectionStringSettings
|
||||
{
|
||||
private string _connectionString;
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Billing.Services;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Services;
|
||||
|
||||
namespace Bit.Core.Utilities;
|
||||
|
||||
public static class BillingHelpers
|
||||
{
|
||||
internal static async Task<string> AdjustStorageAsync(IPaymentService paymentService, IStorableSubscriber storableSubscriber,
|
||||
internal static async Task<string> AdjustStorageAsync(IStripePaymentService paymentService, IStorableSubscriber storableSubscriber,
|
||||
short storageAdjustmentGb, string storagePlanId, short baseStorageGb)
|
||||
{
|
||||
if (storableSubscriber == null)
|
||||
|
||||
@@ -381,7 +381,7 @@ public class OrganizationAbilityService
|
||||
|
||||
### Example Usage: Default (Ephemeral Data)
|
||||
|
||||
#### 1. Registration (already done in Api, Admin, Billing, Identity, and Notifications Startup.cs files, plus Events and EventsProcessor service collection extensions):
|
||||
#### 1. Registration (already done in Api, Admin, Billing, Events, EventsProcessor, Identity, and Notifications Startup.cs files):
|
||||
|
||||
```csharp
|
||||
services.AddDistributedCache(globalSettings);
|
||||
|
||||
@@ -55,16 +55,16 @@ public static class EventIntegrationsCacheConstants
|
||||
/// Builds a deterministic cache key for an organization's integration configuration details
|
||||
/// <see cref="OrganizationIntegrationConfigurationDetails"/>.
|
||||
/// </summary>
|
||||
/// <param name="organizationId">The unique identifier of the organization to which the user belongs.</param>
|
||||
/// <param name="organizationId">The unique identifier of the organization.</param>
|
||||
/// <param name="integrationType">The <see cref="IntegrationType"/> of the integration.</param>
|
||||
/// <param name="eventType">The <see cref="EventType"/> of the event configured. Can be null to apply to all events.</param>
|
||||
/// <param name="eventType">The specific <see cref="EventType"/> of the event configured.</param>
|
||||
/// <returns>
|
||||
/// A cache key for the configuration details.
|
||||
/// </returns>
|
||||
public static string BuildCacheKeyForOrganizationIntegrationConfigurationDetails(
|
||||
Guid organizationId,
|
||||
IntegrationType integrationType,
|
||||
EventType? eventType
|
||||
EventType eventType
|
||||
) => $"OrganizationIntegrationConfigurationDetails:{organizationId:N}:{integrationType}:{eventType}";
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -84,6 +84,8 @@ public class Startup
|
||||
services.AddHostedService<Core.HostedServices.ApplicationCacheHostedService>();
|
||||
}
|
||||
|
||||
// Add event integration services
|
||||
services.AddDistributedCache(globalSettings);
|
||||
services.AddRabbitMqListeners(globalSettings);
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ using Azure.Storage.Queues;
|
||||
using Bit.Core;
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Settings;
|
||||
using Bit.Core.Utilities;
|
||||
|
||||
namespace Bit.EventsProcessor;
|
||||
@@ -13,7 +14,7 @@ namespace Bit.EventsProcessor;
|
||||
public class AzureQueueHostedService : IHostedService, IDisposable
|
||||
{
|
||||
private readonly ILogger<AzureQueueHostedService> _logger;
|
||||
private readonly IConfiguration _configuration;
|
||||
private readonly GlobalSettings _globalSettings;
|
||||
|
||||
private Task _executingTask;
|
||||
private CancellationTokenSource _cts;
|
||||
@@ -22,10 +23,10 @@ public class AzureQueueHostedService : IHostedService, IDisposable
|
||||
|
||||
public AzureQueueHostedService(
|
||||
ILogger<AzureQueueHostedService> logger,
|
||||
IConfiguration configuration)
|
||||
GlobalSettings globalSettings)
|
||||
{
|
||||
_logger = logger;
|
||||
_configuration = configuration;
|
||||
_globalSettings = globalSettings;
|
||||
}
|
||||
|
||||
public Task StartAsync(CancellationToken cancellationToken)
|
||||
@@ -56,11 +57,12 @@ public class AzureQueueHostedService : IHostedService, IDisposable
|
||||
|
||||
private async Task ExecuteAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var storageConnectionString = _configuration["azureStorageConnectionString"];
|
||||
var queueName = _configuration["azureQueueServiceQueueName"];
|
||||
var storageConnectionString = _globalSettings.Events.ConnectionString;
|
||||
var queueName = _globalSettings.Events.QueueName;
|
||||
if (string.IsNullOrWhiteSpace(storageConnectionString) ||
|
||||
string.IsNullOrWhiteSpace(queueName))
|
||||
{
|
||||
_logger.LogInformation("Azure Queue Hosted Service is disabled. Missing connection string or queue name.");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -31,7 +31,8 @@ public class Startup
|
||||
// Repositories
|
||||
services.AddDatabaseRepositories(globalSettings);
|
||||
|
||||
// Hosted Services
|
||||
// Add event integration services
|
||||
services.AddDistributedCache(globalSettings);
|
||||
services.AddAzureServiceBusListeners(globalSettings);
|
||||
services.AddHostedService<AzureQueueHostedService>();
|
||||
}
|
||||
|
||||
@@ -100,167 +100,16 @@ public abstract class BaseRequestValidator<T> where T : class
|
||||
protected async Task ValidateAsync(T context, ValidatedTokenRequest request,
|
||||
CustomValidatorRequestContext validatorContext)
|
||||
{
|
||||
if (_featureService.IsEnabled(FeatureFlagKeys.RecoveryCodeSupportForSsoRequiredUsers))
|
||||
var validators = DetermineValidationOrder(context, request, validatorContext);
|
||||
var allValidationSchemesSuccessful = await ProcessValidatorsAsync(validators);
|
||||
if (!allValidationSchemesSuccessful)
|
||||
{
|
||||
var validators = DetermineValidationOrder(context, request, validatorContext);
|
||||
var allValidationSchemesSuccessful = await ProcessValidatorsAsync(validators);
|
||||
if (!allValidationSchemesSuccessful)
|
||||
{
|
||||
// Each validation task is responsible for setting its own non-success status, if applicable.
|
||||
return;
|
||||
}
|
||||
await BuildSuccessResultAsync(validatorContext.User, context, validatorContext.Device,
|
||||
validatorContext.RememberMeRequested);
|
||||
// Each validation task is responsible for setting its own non-success status, if applicable.
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 1. We need to check if the user is legitimate via the contextually appropriate mechanism
|
||||
// (webauthn, password, custom token, etc.).
|
||||
var valid = await ValidateContextAsync(context, validatorContext);
|
||||
var user = validatorContext.User;
|
||||
if (!valid)
|
||||
{
|
||||
await UpdateFailedAuthDetailsAsync(user);
|
||||
|
||||
await BuildErrorResultAsync("Username or password is incorrect. Try again.", false, context, user);
|
||||
return;
|
||||
}
|
||||
|
||||
// 1.5 Now check the version number of the client. Do this after ValidateContextAsync so that
|
||||
// we prevent account enumeration. If we were to do this before ValidateContextAsync, then attackers
|
||||
// could use a known invalid client version and make a request for a user (before we know if they have
|
||||
// demonstrated ownership of the account via correct credentials) and identify if they exist by getting
|
||||
// an error response back from the validator saying the user is not compatible with the client.
|
||||
var clientVersionValid = await ValidateClientVersionAsync(context, validatorContext);
|
||||
if (!clientVersionValid)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. Decide if this user belongs to an organization that requires SSO.
|
||||
// TODO: Clean up Feature Flag: Remove this if block: PM-28281
|
||||
if (!_featureService.IsEnabled(FeatureFlagKeys.RedirectOnSsoRequired))
|
||||
{
|
||||
validatorContext.SsoRequired = await RequireSsoLoginAsync(user, request.GrantType);
|
||||
if (validatorContext.SsoRequired)
|
||||
{
|
||||
SetSsoResult(context,
|
||||
new Dictionary<string, object>
|
||||
{
|
||||
{ "ErrorModel", new ErrorResponseModel("SSO authentication is required.") }
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var ssoValid = await _ssoRequestValidator.ValidateAsync(user, request, validatorContext);
|
||||
if (!ssoValid)
|
||||
{
|
||||
// SSO is required
|
||||
SetValidationErrorResult(context, validatorContext);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Check if 2FA is required.
|
||||
(validatorContext.TwoFactorRequired, var twoFactorOrganization) =
|
||||
await _twoFactorAuthenticationValidator.RequiresTwoFactorAsync(user, request);
|
||||
|
||||
// This flag is used to determine if the user wants a rememberMe token sent when
|
||||
// authentication is successful.
|
||||
var returnRememberMeToken = false;
|
||||
|
||||
if (validatorContext.TwoFactorRequired)
|
||||
{
|
||||
var twoFactorToken = request.Raw["TwoFactorToken"];
|
||||
var twoFactorProvider = request.Raw["TwoFactorProvider"];
|
||||
var validTwoFactorRequest = !string.IsNullOrWhiteSpace(twoFactorToken) &&
|
||||
!string.IsNullOrWhiteSpace(twoFactorProvider);
|
||||
|
||||
// 3a. Response for 2FA required and not provided state.
|
||||
if (!validTwoFactorRequest ||
|
||||
!Enum.TryParse(twoFactorProvider, out TwoFactorProviderType twoFactorProviderType))
|
||||
{
|
||||
var resultDict = await _twoFactorAuthenticationValidator
|
||||
.BuildTwoFactorResultAsync(user, twoFactorOrganization);
|
||||
if (resultDict == null)
|
||||
{
|
||||
await BuildErrorResultAsync("No two-step providers enabled.", false, context, user);
|
||||
return;
|
||||
}
|
||||
|
||||
// Include Master Password Policy in 2FA response.
|
||||
resultDict.Add("MasterPasswordPolicy", await GetMasterPasswordPolicyAsync(user));
|
||||
SetTwoFactorResult(context, resultDict);
|
||||
return;
|
||||
}
|
||||
|
||||
var twoFactorTokenValid =
|
||||
await _twoFactorAuthenticationValidator
|
||||
.VerifyTwoFactorAsync(user, twoFactorOrganization, twoFactorProviderType, twoFactorToken);
|
||||
|
||||
// 3b. Response for 2FA required but request is not valid or remember token expired state.
|
||||
if (!twoFactorTokenValid)
|
||||
{
|
||||
// The remember me token has expired.
|
||||
if (twoFactorProviderType == TwoFactorProviderType.Remember)
|
||||
{
|
||||
var resultDict = await _twoFactorAuthenticationValidator
|
||||
.BuildTwoFactorResultAsync(user, twoFactorOrganization);
|
||||
|
||||
// Include Master Password Policy in 2FA response
|
||||
resultDict.Add("MasterPasswordPolicy", await GetMasterPasswordPolicyAsync(user));
|
||||
SetTwoFactorResult(context, resultDict);
|
||||
}
|
||||
else
|
||||
{
|
||||
await SendFailedTwoFactorEmail(user, twoFactorProviderType);
|
||||
await UpdateFailedAuthDetailsAsync(user);
|
||||
await BuildErrorResultAsync("Two-step token is invalid. Try again.", true, context, user);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// 3c. When the 2FA authentication is successful, we can check if the user wants a
|
||||
// rememberMe token.
|
||||
var twoFactorRemember = request.Raw["TwoFactorRemember"] == "1";
|
||||
// Check if the user wants a rememberMe token.
|
||||
if (twoFactorRemember
|
||||
// if the 2FA auth was rememberMe do not send another token.
|
||||
&& twoFactorProviderType != TwoFactorProviderType.Remember)
|
||||
{
|
||||
returnRememberMeToken = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Check if the user is logging in from a new device.
|
||||
var deviceValid = await _deviceValidator.ValidateRequestDeviceAsync(request, validatorContext);
|
||||
if (!deviceValid)
|
||||
{
|
||||
SetValidationErrorResult(context, validatorContext);
|
||||
await LogFailedLoginEvent(validatorContext.User, EventType.User_FailedLogIn);
|
||||
return;
|
||||
}
|
||||
|
||||
// 5. Force legacy users to the web for migration.
|
||||
if (UserService.IsLegacyUser(user) && request.ClientId != "web")
|
||||
{
|
||||
await FailAuthForLegacyUserAsync(user, context);
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: PM-24324 - This should be its own validator at some point.
|
||||
// 6. Auth request handling
|
||||
if (validatorContext.ValidatedAuthRequest != null)
|
||||
{
|
||||
validatorContext.ValidatedAuthRequest.AuthenticationDate = DateTime.UtcNow;
|
||||
await _authRequestRepository.ReplaceAsync(validatorContext.ValidatedAuthRequest);
|
||||
}
|
||||
|
||||
await BuildSuccessResultAsync(user, context, validatorContext.Device, returnRememberMeToken);
|
||||
}
|
||||
await BuildSuccessResultAsync(validatorContext.User, context, validatorContext.Device,
|
||||
validatorContext.RememberMeRequested);
|
||||
}
|
||||
|
||||
protected async Task FailAuthForLegacyUserAsync(User user, T context)
|
||||
@@ -291,6 +140,11 @@ public abstract class BaseRequestValidator<T> where T : class
|
||||
return
|
||||
[
|
||||
() => ValidateGrantSpecificContext(context, validatorContext),
|
||||
// Now check the version number of the client. Do this after ValidateContextAsync so that
|
||||
// we prevent account enumeration. If we were to do this before ValidateContextAsync, then attackers
|
||||
// could use a known invalid client version and make a request for a user (before we know if they have
|
||||
// demonstrated ownership of the account via correct credentials) and identify if they exist by getting
|
||||
// an error response back from the validator saying the user is not compatible with the client.
|
||||
() => ValidateClientVersionAsync(context, validatorContext),
|
||||
() => ValidateTwoFactorAsync(context, request, validatorContext),
|
||||
() => ValidateSsoAsync(context, request, validatorContext),
|
||||
@@ -305,6 +159,11 @@ public abstract class BaseRequestValidator<T> where T : class
|
||||
return
|
||||
[
|
||||
() => ValidateGrantSpecificContext(context, validatorContext),
|
||||
// Now check the version number of the client. Do this after ValidateContextAsync so that
|
||||
// we prevent account enumeration. If we were to do this before ValidateContextAsync, then attackers
|
||||
// could use a known invalid client version and make a request for a user (before we know if they have
|
||||
// demonstrated ownership of the account via correct credentials) and identify if they exist by getting
|
||||
// an error response back from the validator saying the user is not compatible with the client.
|
||||
() => ValidateClientVersionAsync(context, validatorContext),
|
||||
() => ValidateSsoAsync(context, request, validatorContext),
|
||||
() => ValidateTwoFactorAsync(context, request, validatorContext),
|
||||
@@ -426,17 +285,22 @@ public abstract class BaseRequestValidator<T> where T : class
|
||||
if (validatorContext.TwoFactorRequired &&
|
||||
validatorContext.TwoFactorRecoveryRequested)
|
||||
{
|
||||
SetSsoResult(context, new Dictionary<string, object>
|
||||
{
|
||||
{ "ErrorModel", new ErrorResponseModel("Two-factor recovery has been performed. SSO authentication is required.") }
|
||||
});
|
||||
SetSsoResult(context,
|
||||
new Dictionary<string, object>
|
||||
{
|
||||
{
|
||||
"ErrorModel",
|
||||
new ErrorResponseModel(
|
||||
"Two-factor recovery has been performed. SSO authentication is required.")
|
||||
}
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
SetSsoResult(context,
|
||||
new Dictionary<string, object>
|
||||
{
|
||||
{ "ErrorModel", new ErrorResponseModel("SSO authentication is required.") }
|
||||
{ "ErrorModel", new ErrorResponseModel("SSO authentication is required.") }
|
||||
});
|
||||
return false;
|
||||
}
|
||||
@@ -717,7 +581,8 @@ public abstract class BaseRequestValidator<T> where T : class
|
||||
/// <param name="user">user trying to login</param>
|
||||
/// <param name="grantType">magic string identifying the grant type requested</param>
|
||||
/// <returns>true if sso required; false if not required or already in process</returns>
|
||||
[Obsolete("This method is deprecated and will be removed in future versions, PM-28281. Please use the SsoRequestValidator scheme instead.")]
|
||||
[Obsolete(
|
||||
"This method is deprecated and will be removed in future versions, PM-28281. Please use the SsoRequestValidator scheme instead.")]
|
||||
private async Task<bool> RequireSsoLoginAsync(User user, string grantType)
|
||||
{
|
||||
if (grantType == "authorization_code" || grantType == "client_credentials")
|
||||
|
||||
@@ -48,8 +48,6 @@ public class SsoRequestValidator(
|
||||
// evaluated, and recovery will have been performed if requested.
|
||||
// We will send a descriptive message in these cases so clients can give the appropriate feedback and redirect
|
||||
// to /login.
|
||||
// If the feature flag RecoveryCodeSupportForSsoRequiredUsers is set to false then this code is unreachable since
|
||||
// Two Factor validation occurs after SSO validation in that scenario.
|
||||
if (context.TwoFactorRequired && context.TwoFactorRecoveryRequested)
|
||||
{
|
||||
await SetContextCustomResponseSsoErrorAsync(context, SsoConstants.RequestErrors.SsoTwoFactorRecoveryDescription);
|
||||
@@ -63,10 +61,10 @@ public class SsoRequestValidator(
|
||||
/// <summary>
|
||||
/// Check if the user is required to authenticate via SSO. If the user requires SSO, but they are
|
||||
/// logging in using an API Key (client_credentials) then they are allowed to bypass the SSO requirement.
|
||||
/// If the GrantType is authorization_code or client_credentials we know the user is trying to login
|
||||
/// If the GrantType is authorization_code or client_credentials we know the user is trying to log in
|
||||
/// using the SSO flow so they are allowed to continue.
|
||||
/// </summary>
|
||||
/// <param name="user">user trying to login</param>
|
||||
/// <param name="user">user trying to log in</param>
|
||||
/// <param name="grantType">magic string identifying the grant type requested</param>
|
||||
/// <returns>true if sso required; false if not required or already in process</returns>
|
||||
private async Task<bool> RequireSsoAuthenticationAsync(User user, string grantType)
|
||||
|
||||
@@ -6,13 +6,9 @@ using System.Reflection;
|
||||
using System.Security.Claims;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using AspNetCoreRateLimit;
|
||||
using Azure.Messaging.ServiceBus;
|
||||
using Bit.Core.AdminConsole.AbilitiesCache;
|
||||
using Bit.Core.AdminConsole.Models.Business.Tokenables;
|
||||
using Bit.Core.AdminConsole.Models.Data.EventIntegrations;
|
||||
using Bit.Core.AdminConsole.Models.Teams;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Policies;
|
||||
using Bit.Core.AdminConsole.Repositories;
|
||||
using Bit.Core.AdminConsole.Services;
|
||||
using Bit.Core.AdminConsole.Services.Implementations;
|
||||
using Bit.Core.AdminConsole.Services.NoopImplementations;
|
||||
@@ -73,8 +69,6 @@ using Microsoft.AspNetCore.HttpOverrides;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc.Localization;
|
||||
using Microsoft.Azure.Cosmos.Fluent;
|
||||
using Microsoft.Bot.Builder;
|
||||
using Microsoft.Bot.Builder.Integration.AspNet.Core;
|
||||
using Microsoft.Extensions.Caching.Cosmos;
|
||||
using Microsoft.Extensions.Caching.Distributed;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
@@ -86,7 +80,6 @@ using Microsoft.Extensions.Options;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using StackExchange.Redis;
|
||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||
using ZiggyCreatures.Caching.Fusion;
|
||||
using Constants = Bit.Core.Constants;
|
||||
using NoopRepos = Bit.Core.Repositories.Noop;
|
||||
using Role = Bit.Core.Entities.Role;
|
||||
@@ -245,7 +238,7 @@ public static class ServiceCollectionExtensions
|
||||
PrivateKey = globalSettings.Braintree.PrivateKey
|
||||
};
|
||||
});
|
||||
services.AddScoped<IPaymentService, StripePaymentService>();
|
||||
services.AddScoped<IStripePaymentService, StripePaymentService>();
|
||||
services.AddScoped<IPaymentHistoryService, PaymentHistoryService>();
|
||||
services.AddScoped<ITwoFactorEmailService, TwoFactorEmailService>();
|
||||
// Legacy mailer service
|
||||
@@ -525,116 +518,6 @@ public static class ServiceCollectionExtensions
|
||||
return globalSettings;
|
||||
}
|
||||
|
||||
public static IServiceCollection AddEventWriteServices(this IServiceCollection services, GlobalSettings globalSettings)
|
||||
{
|
||||
if (IsAzureServiceBusEnabled(globalSettings))
|
||||
{
|
||||
services.TryAddSingleton<IEventIntegrationPublisher, AzureServiceBusService>();
|
||||
services.TryAddSingleton<IEventWriteService, EventIntegrationEventWriteService>();
|
||||
return services;
|
||||
}
|
||||
|
||||
if (IsRabbitMqEnabled(globalSettings))
|
||||
{
|
||||
services.TryAddSingleton<IEventIntegrationPublisher, RabbitMqService>();
|
||||
services.TryAddSingleton<IEventWriteService, EventIntegrationEventWriteService>();
|
||||
return services;
|
||||
}
|
||||
|
||||
if (CoreHelpers.SettingHasValue(globalSettings.Events.ConnectionString))
|
||||
{
|
||||
services.TryAddSingleton<IEventWriteService, AzureQueueEventWriteService>();
|
||||
return services;
|
||||
}
|
||||
|
||||
if (globalSettings.SelfHosted)
|
||||
{
|
||||
services.TryAddSingleton<IEventWriteService, RepositoryEventWriteService>();
|
||||
return services;
|
||||
}
|
||||
|
||||
services.TryAddSingleton<IEventWriteService, NoopEventWriteService>();
|
||||
return services;
|
||||
}
|
||||
|
||||
public static IServiceCollection AddAzureServiceBusListeners(this IServiceCollection services, GlobalSettings globalSettings)
|
||||
{
|
||||
if (!IsAzureServiceBusEnabled(globalSettings))
|
||||
{
|
||||
return services;
|
||||
}
|
||||
|
||||
services.TryAddSingleton<IAzureServiceBusService, AzureServiceBusService>();
|
||||
services.TryAddSingleton<IEventIntegrationPublisher, AzureServiceBusService>();
|
||||
services.TryAddSingleton<IEventRepository, TableStorageRepos.EventRepository>();
|
||||
services.TryAddKeyedSingleton<IEventWriteService, RepositoryEventWriteService>("persistent");
|
||||
services.TryAddSingleton<AzureTableStorageEventHandler>();
|
||||
|
||||
services.AddEventIntegrationServices(globalSettings);
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
public static IServiceCollection AddRabbitMqListeners(this IServiceCollection services, GlobalSettings globalSettings)
|
||||
{
|
||||
if (!IsRabbitMqEnabled(globalSettings))
|
||||
{
|
||||
return services;
|
||||
}
|
||||
|
||||
services.TryAddSingleton<IRabbitMqService, RabbitMqService>();
|
||||
services.TryAddSingleton<IEventIntegrationPublisher, RabbitMqService>();
|
||||
services.TryAddSingleton<EventRepositoryHandler>();
|
||||
|
||||
services.AddEventIntegrationServices(globalSettings);
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
public static IServiceCollection AddSlackService(this IServiceCollection services, GlobalSettings globalSettings)
|
||||
{
|
||||
if (CoreHelpers.SettingHasValue(globalSettings.Slack.ClientId) &&
|
||||
CoreHelpers.SettingHasValue(globalSettings.Slack.ClientSecret) &&
|
||||
CoreHelpers.SettingHasValue(globalSettings.Slack.Scopes))
|
||||
{
|
||||
services.AddHttpClient(SlackService.HttpClientName);
|
||||
services.TryAddSingleton<ISlackService, SlackService>();
|
||||
}
|
||||
else
|
||||
{
|
||||
services.TryAddSingleton<ISlackService, NoopSlackService>();
|
||||
}
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
public static IServiceCollection AddTeamsService(this IServiceCollection services, GlobalSettings globalSettings)
|
||||
{
|
||||
if (CoreHelpers.SettingHasValue(globalSettings.Teams.ClientId) &&
|
||||
CoreHelpers.SettingHasValue(globalSettings.Teams.ClientSecret) &&
|
||||
CoreHelpers.SettingHasValue(globalSettings.Teams.Scopes))
|
||||
{
|
||||
services.AddHttpClient(TeamsService.HttpClientName);
|
||||
services.TryAddSingleton<TeamsService>();
|
||||
services.TryAddSingleton<IBot>(sp => sp.GetRequiredService<TeamsService>());
|
||||
services.TryAddSingleton<ITeamsService>(sp => sp.GetRequiredService<TeamsService>());
|
||||
services.TryAddSingleton<IBotFrameworkHttpAdapter>(sp =>
|
||||
new BotFrameworkHttpAdapter(
|
||||
new TeamsBotCredentialProvider(
|
||||
clientId: globalSettings.Teams.ClientId,
|
||||
clientSecret: globalSettings.Teams.ClientSecret
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
services.TryAddSingleton<ITeamsService, NoopTeamsService>();
|
||||
}
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
public static void UseDefaultMiddleware(this IApplicationBuilder app,
|
||||
IWebHostEnvironment env, GlobalSettings globalSettings)
|
||||
{
|
||||
@@ -881,186 +764,6 @@ public static class ServiceCollectionExtensions
|
||||
return (provider, connectionString);
|
||||
}
|
||||
|
||||
private static IServiceCollection AddAzureServiceBusIntegration<TConfig, TListenerConfig>(this IServiceCollection services,
|
||||
TListenerConfig listenerConfiguration)
|
||||
where TConfig : class
|
||||
where TListenerConfig : IIntegrationListenerConfiguration
|
||||
{
|
||||
services.TryAddKeyedSingleton<IEventMessageHandler>(serviceKey: listenerConfiguration.RoutingKey, implementationFactory: (provider, _) =>
|
||||
new EventIntegrationHandler<TConfig>(
|
||||
integrationType: listenerConfiguration.IntegrationType,
|
||||
eventIntegrationPublisher: provider.GetRequiredService<IEventIntegrationPublisher>(),
|
||||
integrationFilterService: provider.GetRequiredService<IIntegrationFilterService>(),
|
||||
cache: provider.GetRequiredKeyedService<IFusionCache>(EventIntegrationsCacheConstants.CacheName),
|
||||
configurationRepository: provider.GetRequiredService<IOrganizationIntegrationConfigurationRepository>(),
|
||||
groupRepository: provider.GetRequiredService<IGroupRepository>(),
|
||||
organizationRepository: provider.GetRequiredService<IOrganizationRepository>(),
|
||||
organizationUserRepository: provider.GetRequiredService<IOrganizationUserRepository>(), logger: provider.GetRequiredService<ILogger<EventIntegrationHandler<TConfig>>>())
|
||||
);
|
||||
services.TryAddEnumerable(ServiceDescriptor.Singleton<IHostedService,
|
||||
AzureServiceBusEventListenerService<TListenerConfig>>(provider =>
|
||||
new AzureServiceBusEventListenerService<TListenerConfig>(
|
||||
configuration: listenerConfiguration,
|
||||
handler: provider.GetRequiredKeyedService<IEventMessageHandler>(serviceKey: listenerConfiguration.RoutingKey),
|
||||
serviceBusService: provider.GetRequiredService<IAzureServiceBusService>(),
|
||||
serviceBusOptions: new ServiceBusProcessorOptions()
|
||||
{
|
||||
PrefetchCount = listenerConfiguration.EventPrefetchCount,
|
||||
MaxConcurrentCalls = listenerConfiguration.EventMaxConcurrentCalls
|
||||
},
|
||||
loggerFactory: provider.GetRequiredService<ILoggerFactory>()
|
||||
)
|
||||
)
|
||||
);
|
||||
services.TryAddEnumerable(ServiceDescriptor.Singleton<IHostedService,
|
||||
AzureServiceBusIntegrationListenerService<TListenerConfig>>(provider =>
|
||||
new AzureServiceBusIntegrationListenerService<TListenerConfig>(
|
||||
configuration: listenerConfiguration,
|
||||
handler: provider.GetRequiredService<IIntegrationHandler<TConfig>>(),
|
||||
serviceBusService: provider.GetRequiredService<IAzureServiceBusService>(),
|
||||
serviceBusOptions: new ServiceBusProcessorOptions()
|
||||
{
|
||||
PrefetchCount = listenerConfiguration.IntegrationPrefetchCount,
|
||||
MaxConcurrentCalls = listenerConfiguration.IntegrationMaxConcurrentCalls
|
||||
},
|
||||
loggerFactory: provider.GetRequiredService<ILoggerFactory>()
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
private static IServiceCollection AddEventIntegrationServices(this IServiceCollection services,
|
||||
GlobalSettings globalSettings)
|
||||
{
|
||||
// Add common services
|
||||
services.AddDistributedCache(globalSettings);
|
||||
services.AddExtendedCache(EventIntegrationsCacheConstants.CacheName, globalSettings);
|
||||
services.TryAddSingleton<IIntegrationFilterService, IntegrationFilterService>();
|
||||
services.TryAddKeyedSingleton<IEventWriteService, RepositoryEventWriteService>("persistent");
|
||||
|
||||
// Add services in support of handlers
|
||||
services.AddSlackService(globalSettings);
|
||||
services.AddTeamsService(globalSettings);
|
||||
services.TryAddSingleton(TimeProvider.System);
|
||||
services.AddHttpClient(WebhookIntegrationHandler.HttpClientName);
|
||||
services.AddHttpClient(DatadogIntegrationHandler.HttpClientName);
|
||||
|
||||
// Add integration handlers
|
||||
services.TryAddSingleton<IIntegrationHandler<SlackIntegrationConfigurationDetails>, SlackIntegrationHandler>();
|
||||
services.TryAddSingleton<IIntegrationHandler<WebhookIntegrationConfigurationDetails>, WebhookIntegrationHandler>();
|
||||
services.TryAddSingleton<IIntegrationHandler<DatadogIntegrationConfigurationDetails>, DatadogIntegrationHandler>();
|
||||
services.TryAddSingleton<IIntegrationHandler<TeamsIntegrationConfigurationDetails>, TeamsIntegrationHandler>();
|
||||
|
||||
var repositoryConfiguration = new RepositoryListenerConfiguration(globalSettings);
|
||||
var slackConfiguration = new SlackListenerConfiguration(globalSettings);
|
||||
var webhookConfiguration = new WebhookListenerConfiguration(globalSettings);
|
||||
var hecConfiguration = new HecListenerConfiguration(globalSettings);
|
||||
var datadogConfiguration = new DatadogListenerConfiguration(globalSettings);
|
||||
var teamsConfiguration = new TeamsListenerConfiguration(globalSettings);
|
||||
|
||||
if (IsRabbitMqEnabled(globalSettings))
|
||||
{
|
||||
services.TryAddEnumerable(ServiceDescriptor.Singleton<IHostedService,
|
||||
RabbitMqEventListenerService<RepositoryListenerConfiguration>>(provider =>
|
||||
new RabbitMqEventListenerService<RepositoryListenerConfiguration>(
|
||||
handler: provider.GetRequiredService<EventRepositoryHandler>(),
|
||||
configuration: repositoryConfiguration,
|
||||
rabbitMqService: provider.GetRequiredService<IRabbitMqService>(),
|
||||
loggerFactory: provider.GetRequiredService<ILoggerFactory>()
|
||||
)
|
||||
)
|
||||
);
|
||||
services.AddRabbitMqIntegration<SlackIntegrationConfigurationDetails, SlackListenerConfiguration>(slackConfiguration);
|
||||
services.AddRabbitMqIntegration<WebhookIntegrationConfigurationDetails, WebhookListenerConfiguration>(webhookConfiguration);
|
||||
services.AddRabbitMqIntegration<WebhookIntegrationConfigurationDetails, HecListenerConfiguration>(hecConfiguration);
|
||||
services.AddRabbitMqIntegration<DatadogIntegrationConfigurationDetails, DatadogListenerConfiguration>(datadogConfiguration);
|
||||
services.AddRabbitMqIntegration<TeamsIntegrationConfigurationDetails, TeamsListenerConfiguration>(teamsConfiguration);
|
||||
}
|
||||
|
||||
if (IsAzureServiceBusEnabled(globalSettings))
|
||||
{
|
||||
services.TryAddEnumerable(ServiceDescriptor.Singleton<IHostedService,
|
||||
AzureServiceBusEventListenerService<RepositoryListenerConfiguration>>(provider =>
|
||||
new AzureServiceBusEventListenerService<RepositoryListenerConfiguration>(
|
||||
configuration: repositoryConfiguration,
|
||||
handler: provider.GetRequiredService<AzureTableStorageEventHandler>(),
|
||||
serviceBusService: provider.GetRequiredService<IAzureServiceBusService>(),
|
||||
serviceBusOptions: new ServiceBusProcessorOptions()
|
||||
{
|
||||
PrefetchCount = repositoryConfiguration.EventPrefetchCount,
|
||||
MaxConcurrentCalls = repositoryConfiguration.EventMaxConcurrentCalls
|
||||
},
|
||||
loggerFactory: provider.GetRequiredService<ILoggerFactory>()
|
||||
)
|
||||
)
|
||||
);
|
||||
services.AddAzureServiceBusIntegration<SlackIntegrationConfigurationDetails, SlackListenerConfiguration>(slackConfiguration);
|
||||
services.AddAzureServiceBusIntegration<WebhookIntegrationConfigurationDetails, WebhookListenerConfiguration>(webhookConfiguration);
|
||||
services.AddAzureServiceBusIntegration<WebhookIntegrationConfigurationDetails, HecListenerConfiguration>(hecConfiguration);
|
||||
services.AddAzureServiceBusIntegration<DatadogIntegrationConfigurationDetails, DatadogListenerConfiguration>(datadogConfiguration);
|
||||
services.AddAzureServiceBusIntegration<TeamsIntegrationConfigurationDetails, TeamsListenerConfiguration>(teamsConfiguration);
|
||||
}
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
private static IServiceCollection AddRabbitMqIntegration<TConfig, TListenerConfig>(this IServiceCollection services,
|
||||
TListenerConfig listenerConfiguration)
|
||||
where TConfig : class
|
||||
where TListenerConfig : IIntegrationListenerConfiguration
|
||||
{
|
||||
services.TryAddKeyedSingleton<IEventMessageHandler>(serviceKey: listenerConfiguration.RoutingKey, implementationFactory: (provider, _) =>
|
||||
new EventIntegrationHandler<TConfig>(
|
||||
integrationType: listenerConfiguration.IntegrationType,
|
||||
eventIntegrationPublisher: provider.GetRequiredService<IEventIntegrationPublisher>(),
|
||||
integrationFilterService: provider.GetRequiredService<IIntegrationFilterService>(),
|
||||
cache: provider.GetRequiredKeyedService<IFusionCache>(EventIntegrationsCacheConstants.CacheName),
|
||||
configurationRepository: provider.GetRequiredService<IOrganizationIntegrationConfigurationRepository>(),
|
||||
groupRepository: provider.GetRequiredService<IGroupRepository>(),
|
||||
organizationRepository: provider.GetRequiredService<IOrganizationRepository>(),
|
||||
organizationUserRepository: provider.GetRequiredService<IOrganizationUserRepository>(), logger: provider.GetRequiredService<ILogger<EventIntegrationHandler<TConfig>>>())
|
||||
);
|
||||
services.TryAddEnumerable(ServiceDescriptor.Singleton<IHostedService,
|
||||
RabbitMqEventListenerService<TListenerConfig>>(provider =>
|
||||
new RabbitMqEventListenerService<TListenerConfig>(
|
||||
handler: provider.GetRequiredKeyedService<IEventMessageHandler>(serviceKey: listenerConfiguration.RoutingKey),
|
||||
configuration: listenerConfiguration,
|
||||
rabbitMqService: provider.GetRequiredService<IRabbitMqService>(),
|
||||
loggerFactory: provider.GetRequiredService<ILoggerFactory>()
|
||||
)
|
||||
)
|
||||
);
|
||||
services.TryAddEnumerable(ServiceDescriptor.Singleton<IHostedService,
|
||||
RabbitMqIntegrationListenerService<TListenerConfig>>(provider =>
|
||||
new RabbitMqIntegrationListenerService<TListenerConfig>(
|
||||
handler: provider.GetRequiredService<IIntegrationHandler<TConfig>>(),
|
||||
configuration: listenerConfiguration,
|
||||
rabbitMqService: provider.GetRequiredService<IRabbitMqService>(),
|
||||
loggerFactory: provider.GetRequiredService<ILoggerFactory>(),
|
||||
timeProvider: provider.GetRequiredService<TimeProvider>()
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
private static bool IsAzureServiceBusEnabled(GlobalSettings settings)
|
||||
{
|
||||
return CoreHelpers.SettingHasValue(settings.EventLogging.AzureServiceBus.ConnectionString) &&
|
||||
CoreHelpers.SettingHasValue(settings.EventLogging.AzureServiceBus.EventTopicName);
|
||||
}
|
||||
|
||||
private static bool IsRabbitMqEnabled(GlobalSettings settings)
|
||||
{
|
||||
return CoreHelpers.SettingHasValue(settings.EventLogging.RabbitMq.HostName) &&
|
||||
CoreHelpers.SettingHasValue(settings.EventLogging.RabbitMq.Username) &&
|
||||
CoreHelpers.SettingHasValue(settings.EventLogging.RabbitMq.Password) &&
|
||||
CoreHelpers.SettingHasValue(settings.EventLogging.RabbitMq.EventExchangeName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a server with its corresponding OAuth2 client credentials security definition and requirement.
|
||||
/// </summary>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user