-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[AC-1449] Secrets Manager subscription Stripe Integration #72
base: main
Are you sure you want to change the base?
Changes from all commits
4c68a03
eeca1d5
d00934c
52f44be
0bd73e6
b8b0bf1
169809b
9722ae5
de0d0dd
c5b0e58
06cfa66
110ee1d
cc695b2
ca38d32
8a00550
ae3fa3c
43bb9d2
2b5c9a1
9ae74a1
c6e821c
cbfd49c
10cc837
a607a05
1403d28
81a72a4
52c1067
8413ba9
9721826
0e13348
906b0c5
15076a3
d487a8f
44a7f82
7fcbf9b
8483484
b8261db
c71cb5a
4f456ac
aa6cdb4
e78e482
cdc2e94
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,6 +18,8 @@ | |
using Bit.Core.Models.Data.Organizations.Policies; | ||
using Bit.Core.OrganizationFeatures.OrganizationApiKeys.Interfaces; | ||
using Bit.Core.OrganizationFeatures.OrganizationLicenses.Interfaces; | ||
using Bit.Core.OrganizationFeatures.OrganizationPlanUpgrade.Interface; | ||
using Bit.Core.OrganizationFeatures.OrganizationSignUp.Interfaces; | ||
using Bit.Core.Repositories; | ||
using Bit.Core.Services; | ||
using Bit.Core.Settings; | ||
|
@@ -50,6 +52,8 @@ public class OrganizationsController : Controller | |
private readonly IFeatureService _featureService; | ||
private readonly GlobalSettings _globalSettings; | ||
private readonly ILicensingService _licensingService; | ||
private readonly IOrganizationSignUpCommand _organizationSignUpCommand; | ||
private readonly IOrganizationUpgradePlanCommand _organizationUpgradePlanCommand; | ||
|
||
public OrganizationsController( | ||
IOrganizationRepository organizationRepository, | ||
|
@@ -70,7 +74,9 @@ public OrganizationsController( | |
ICloudGetOrganizationLicenseQuery cloudGetOrganizationLicenseQuery, | ||
IFeatureService featureService, | ||
GlobalSettings globalSettings, | ||
ILicensingService licensingService) | ||
ILicensingService licensingService, | ||
IOrganizationSignUpCommand organizationSignUpCommand, | ||
IOrganizationUpgradePlanCommand organizationUpgradePlanCommand) | ||
{ | ||
_organizationRepository = organizationRepository; | ||
_organizationUserRepository = organizationUserRepository; | ||
|
@@ -91,6 +97,8 @@ public OrganizationsController( | |
_featureService = featureService; | ||
_globalSettings = globalSettings; | ||
_licensingService = licensingService; | ||
_organizationSignUpCommand = organizationSignUpCommand; | ||
_organizationUpgradePlanCommand = organizationUpgradePlanCommand; | ||
} | ||
|
||
[HttpGet("{id}")] | ||
|
@@ -241,7 +249,12 @@ public async Task<OrganizationResponseModel> Post([FromBody] OrganizationCreateR | |
} | ||
|
||
var organizationSignup = model.ToOrganizationSignup(user); | ||
var result = await _organizationService.SignUpAsync(organizationSignup); | ||
|
||
var result = !_featureService.IsEnabled(FeatureFlagKeys.SecretManagerGaBilling, _currentContext) && | ||
!model.UseSecretsManager | ||
? await _organizationService.SignUpAsync(organizationSignup) | ||
: await _organizationSignUpCommand.SignUpAsync(organizationSignup); | ||
|
||
return new OrganizationResponseModel(result.Item1); | ||
} | ||
|
||
|
@@ -306,7 +319,11 @@ public async Task<PaymentResponseModel> PostUpgrade(string id, [FromBody] Organi | |
throw new NotFoundException(); | ||
} | ||
|
||
var result = await _organizationService.UpgradePlanAsync(orgIdGuid, model.ToOrganizationUpgrade()); | ||
var result = !_featureService.IsEnabled(FeatureFlagKeys.SecretManagerGaBilling, _currentContext) && | ||
!model.UseSecretsManager | ||
? await _organizationService.UpgradePlanAsync(orgIdGuid, model.ToOrganizationUpgrade()) | ||
: await _organizationUpgradePlanCommand.UpgradePlanAsync(orgIdGuid, model.ToOrganizationUpgrade()); | ||
Comment on lines
+322
to
+325
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. style: This condition is identical to the one in the Post method. Consider extracting it to avoid duplication |
||
|
||
return new PaymentResponseModel { Success = result.Item1, PaymentIntentClientSecret = result.Item2 }; | ||
} | ||
|
||
|
@@ -487,7 +504,7 @@ public async Task<ApiKeyResponseModel> ApiKey(string id, [FromBody] Organization | |
if (model.Type == OrganizationApiKeyType.BillingSync || model.Type == OrganizationApiKeyType.Scim) | ||
{ | ||
// Non-enterprise orgs should not be able to create or view an apikey of billing sync/scim key types | ||
var plan = StaticStore.GetPlan(organization.PlanType); | ||
var plan = StaticStore.GetPasswordManagerPlan(organization.PlanType); | ||
if (plan.Product != ProductType.Enterprise) | ||
{ | ||
throw new NotFoundException(); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,12 +19,30 @@ public PlansController(ITaxRateRepository taxRateRepository) | |
[HttpGet("")] | ||
[AllowAnonymous] | ||
public ListResponseModel<PlanResponseModel> Get() | ||
{ | ||
var data = StaticStore.PasswordManagerPlans; | ||
var responses = data.Select(plan => new PlanResponseModel(plan)); | ||
return new ListResponseModel<PlanResponseModel>(responses); | ||
} | ||
Comment on lines
21
to
+26
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. logic: The default Get() method now returns only PasswordManagerPlans instead of all Plans. This change might affect existing clients expecting all plans. Consider adding a comment explaining this change or updating documentation. |
||
|
||
[HttpGet("all")] | ||
[AllowAnonymous] | ||
public ListResponseModel<PlanResponseModel> GetAllPlans() | ||
{ | ||
var data = StaticStore.Plans; | ||
var responses = data.Select(plan => new PlanResponseModel(plan)); | ||
return new ListResponseModel<PlanResponseModel>(responses); | ||
} | ||
|
||
[HttpGet("sm-plans")] | ||
[AllowAnonymous] | ||
public ListResponseModel<PlanResponseModel> GetSecretsManagerPlans() | ||
{ | ||
var data = StaticStore.SecretManagerPlans; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. syntax: SecretManagerPlans is misspelled. It should be SecretsManagerPlans to maintain consistency with the method name. |
||
var responses = data.Select(plan => new PlanResponseModel(plan)); | ||
return new ListResponseModel<PlanResponseModel>(responses); | ||
} | ||
|
||
[HttpGet("sales-tax-rates")] | ||
public async Task<ListResponseModel<TaxRateResponseModel>> GetTaxRates() | ||
{ | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -40,6 +40,12 @@ public class OrganizationCreateRequestModel : IValidatableObject | |
[StringLength(2)] | ||
public string BillingAddressCountry { get; set; } | ||
public int? MaxAutoscaleSeats { get; set; } | ||
[Range(0, int.MaxValue)] | ||
public int? AdditionalSmSeats { get; set; } | ||
[Range(0, int.MaxValue)] | ||
public int? AdditionalServiceAccount { get; set; } | ||
[Required] | ||
public bool UseSecretsManager { get; set; } | ||
|
||
public virtual OrganizationSignup ToOrganizationSignup(User user) | ||
{ | ||
|
@@ -58,6 +64,9 @@ public virtual OrganizationSignup ToOrganizationSignup(User user) | |
BillingEmail = BillingEmail, | ||
BusinessName = BusinessName, | ||
CollectionName = CollectionName, | ||
AdditionalSmSeats = AdditionalSmSeats.GetValueOrDefault(0), | ||
AdditionalServiceAccount = AdditionalServiceAccount.GetValueOrDefault(0), | ||
UseSecretsManager = UseSecretsManager, | ||
Comment on lines
+67
to
+69
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. style: Use null-coalescing operator (??) instead of GetValueOrDefault for consistency with other properties |
||
TaxInfo = new TaxInfo | ||
{ | ||
TaxIdNumber = TaxIdNumber, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,7 +13,13 @@ public class OrganizationUpgradeRequestModel | |
public int AdditionalSeats { get; set; } | ||
[Range(0, 99)] | ||
public short? AdditionalStorageGb { get; set; } | ||
[Range(0, int.MaxValue)] | ||
public int? AdditionalSmSeats { get; set; } | ||
[Range(0, int.MaxValue)] | ||
public int? AdditionalServiceAccount { get; set; } | ||
public bool PremiumAccessAddon { get; set; } | ||
[Required] | ||
public bool UseSecretsManager { get; set; } | ||
public string BillingAddressCountry { get; set; } | ||
public string BillingAddressPostalCode { get; set; } | ||
public OrganizationKeysRequestModel Keys { get; set; } | ||
|
@@ -24,6 +30,9 @@ public OrganizationUpgrade ToOrganizationUpgrade() | |
{ | ||
AdditionalSeats = AdditionalSeats, | ||
AdditionalStorageGb = AdditionalStorageGb.GetValueOrDefault(), | ||
AdditionalServiceAccount = AdditionalServiceAccount.GetValueOrDefault(0), | ||
AdditionalSmSeats = AdditionalSmSeats.GetValueOrDefault(0), | ||
Comment on lines
+33
to
+34
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. style: Use null-coalescing operator for consistency: |
||
UseSecretsManager = UseSecretsManager, | ||
Comment on lines
+34
to
+35
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. style: Use null-coalescing operator for consistency: |
||
BusinessName = BusinessName, | ||
Plan = PlanType, | ||
PremiumAccessAddon = PremiumAccessAddon, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -54,7 +54,7 @@ public ProfileOrganizationResponseModel(OrganizationUserOrganizationDetails orga | |
FamilySponsorshipAvailable = FamilySponsorshipFriendlyName == null && | ||
StaticStore.GetSponsoredPlan(PlanSponsorshipType.FamiliesForEnterprise) | ||
.UsersCanSponsor(organization); | ||
PlanProductType = StaticStore.GetPlan(organization.PlanType).Product; | ||
PlanProductType = StaticStore.GetPasswordManagerPlan(organization.PlanType).Product; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. logic: Ensure that GetPasswordManagerPlan() returns the correct plan for all organization types, including those that might not use the password manager. |
||
FamilySponsorshipLastSyncDate = organization.FamilySponsorshipLastSyncDate; | ||
FamilySponsorshipToDelete = organization.FamilySponsorshipToDelete; | ||
FamilySponsorshipValidUntil = organization.FamilySponsorshipValidUntil; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -42,6 +42,6 @@ public ProfileProviderOrganizationResponseModel(ProviderUserOrganizationDetails | |
UserId = organization.UserId?.ToString(); | ||
ProviderId = organization.ProviderId?.ToString(); | ||
ProviderName = organization.ProviderName; | ||
PlanProductType = StaticStore.GetPlan(organization.PlanType).Product; | ||
PlanProductType = StaticStore.GetPasswordManagerPlan(organization.PlanType).Product; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. logic: Ensure that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. style: Consider adding a null check before accessing the |
||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -417,7 +417,7 @@ await _transactionRepository.CreateAsync(new Transaction | |
// org | ||
if (ids.Item1.HasValue) | ||
{ | ||
if (subscription.Items.Any(i => StaticStore.Plans.Any(p => p.StripePlanId == i.Plan.Id))) | ||
if (subscription.Items.Any(i => StaticStore.PasswordManagerPlans.Any(p => p.StripePlanId == i.Plan.Id))) | ||
{ | ||
await _organizationService.EnableAsync(ids.Item1.Value, subscription.CurrentPeriodEnd); | ||
|
||
Comment on lines
+420
to
423
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. logic: This condition now only checks for Password Manager plans. Consider adding a check for Secrets Manager plans as well to ensure proper handling of both types of subscriptions. |
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
using System.ComponentModel.DataAnnotations; | ||
|
||
namespace Bit.Core.Enums; | ||
|
||
public enum BitwardenProductType : byte | ||
{ | ||
[Display(Name = "Password Manager")] | ||
PasswordManager = 0, | ||
[Display(Name = "Secrets Manager")] | ||
SecretsManager = 1, | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,4 +12,7 @@ public class OrganizationUpgrade | |
public TaxInfo TaxInfo { get; set; } | ||
public string PublicKey { get; set; } | ||
public string PrivateKey { get; set; } | ||
public int? AdditionalSmSeats { get; set; } | ||
public int? AdditionalServiceAccount { get; set; } | ||
public bool UseSecretsManager { get; set; } | ||
Comment on lines
+15
to
+17
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. style: consider adding XML documentation comments for these new properties |
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
using Bit.Core.Entities; | ||
using Bit.Core.Enums; | ||
using Stripe; | ||
|
||
namespace Bit.Core.Models.Business; | ||
|
@@ -54,6 +55,80 @@ public OrganizationSubscriptionOptionsBase(Organization org, StaticStore.Plan pl | |
DefaultTaxRates = new List<string> { taxInfo.StripeTaxRateId }; | ||
} | ||
} | ||
|
||
public OrganizationSubscriptionOptionsBase(Organization org, IEnumerable<StaticStore.Plan> plans, TaxInfo taxInfo, int additionalSeats | ||
, int additionalStorageGb, bool premiumAccessAddon, int additionalSmSeats = 0, int additionalServiceAccount = 0) | ||
{ | ||
Comment on lines
+59
to
+61
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. style: Consider using nullable types for optional parameters |
||
Items = new List<SubscriptionItemOptions>(); | ||
Metadata = new Dictionary<string, string> | ||
{ | ||
[org.GatewayIdField()] = org.Id.ToString() | ||
}; | ||
|
||
foreach (var plan in plans) | ||
{ | ||
if (plan.StripePlanId != null) | ||
{ | ||
Items.Add(new SubscriptionItemOptions | ||
{ | ||
Plan = plan.StripePlanId, | ||
Quantity = 1 | ||
}); | ||
} | ||
|
||
if (additionalSeats > 0 && plan.StripeSeatPlanId != null && plan.BitwardenProduct == BitwardenProductType.PasswordManager) | ||
{ | ||
Items.Add(new SubscriptionItemOptions | ||
{ | ||
Plan = plan.StripeSeatPlanId, | ||
Quantity = additionalSeats | ||
}); | ||
} | ||
Comment on lines
+79
to
+86
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. style: Duplicate logic for PasswordManager. Consider extracting to a separate method |
||
|
||
if (additionalStorageGb > 0 && plan.BitwardenProduct == BitwardenProductType.PasswordManager) | ||
{ | ||
Items.Add(new SubscriptionItemOptions | ||
{ | ||
Plan = plan.StripeStoragePlanId, | ||
Quantity = additionalStorageGb | ||
}); | ||
} | ||
|
||
if (additionalSmSeats > 0 && plan.StripePlanId != null && plan.BitwardenProduct == BitwardenProductType.SecretsManager) | ||
{ | ||
Items.Add(new SubscriptionItemOptions | ||
{ | ||
Plan = plan.StripePlanId, | ||
Quantity = additionalSmSeats | ||
}); | ||
Comment on lines
+97
to
+103
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. logic: Potential issue: using StripePlanId for SecretsManager seats instead of a dedicated seat plan ID |
||
} | ||
|
||
if (additionalServiceAccount > 0 && plan.StripePlanId != null && plan.BitwardenProduct == BitwardenProductType.SecretsManager) | ||
{ | ||
Items.Add(new SubscriptionItemOptions | ||
{ | ||
Plan = plan.StripePlanId, | ||
Quantity = additionalServiceAccount | ||
}); | ||
Comment on lines
+106
to
+112
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. logic: Potential issue: using StripePlanId for service accounts instead of a dedicated plan ID |
||
} | ||
|
||
if (premiumAccessAddon && plan.StripePremiumAccessPlanId != null && plan.BitwardenProduct == BitwardenProductType.PasswordManager) | ||
{ | ||
Items.Add(new SubscriptionItemOptions | ||
{ | ||
Plan = plan.StripePremiumAccessPlanId, | ||
Quantity = 1 | ||
}); | ||
} | ||
} | ||
|
||
|
||
if (!string.IsNullOrWhiteSpace(taxInfo?.StripeTaxRateId)) | ||
{ | ||
DefaultTaxRates = new List<string> { taxInfo.StripeTaxRateId }; | ||
} | ||
|
||
} | ||
} | ||
|
||
public class OrganizationPurchaseSubscriptionOptions : OrganizationSubscriptionOptionsBase | ||
|
@@ -67,6 +142,15 @@ public OrganizationPurchaseSubscriptionOptions( | |
OffSession = true; | ||
TrialPeriodDays = plan.TrialPeriodDays; | ||
} | ||
|
||
public OrganizationPurchaseSubscriptionOptions(Organization org, IEnumerable<StaticStore.Plan> plans, TaxInfo taxInfo, int additionalSeats = 0 | ||
, int additionalStorageGb = 0, bool premiumAccessAddon = false | ||
, int additionalSmSeats = 0, int additionalServiceAccount = 0) : | ||
base(org, plans, taxInfo, additionalSeats, additionalStorageGb, premiumAccessAddon, additionalSmSeats, additionalServiceAccount) | ||
{ | ||
OffSession = true; | ||
TrialPeriodDays = plans.FirstOrDefault().TrialPeriodDays; | ||
} | ||
Comment on lines
+146
to
+153
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. logic: Consider adding null check for plans.FirstOrDefault() |
||
} | ||
|
||
public class OrganizationUpgradeSubscriptionOptions : OrganizationSubscriptionOptionsBase | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -52,4 +52,9 @@ public class Plan | |
public decimal SeatPrice { get; set; } | ||
public decimal AdditionalStoragePricePerGb { get; set; } | ||
public decimal PremiumAccessOptionPrice { get; set; } | ||
public decimal? AdditionalPricePerServiceAccount { get; set; } | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. style: Consider adding XML documentation for this new property to explain its purpose and usage. |
||
public short? BaseServiceAccount { get; set; } | ||
public short? MaxServiceAccount { get; set; } | ||
Comment on lines
+56
to
+57
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. style: These properties should probably be nullable ints (int?) instead of nullable shorts (short?) for consistency with other similar properties in the class (e.g., MaxUsers). |
||
public bool HasAdditionalServiceAccountOption { get; set; } | ||
public BitwardenProductType BitwardenProduct { get; set; } | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
using Bit.Core.Models.Business; | ||
|
||
namespace Bit.Core.OrganizationFeatures.OrganizationPlanUpgrade.Interface; | ||
|
||
public interface IOrganizationUpgradePlanCommand | ||
{ | ||
Task<Tuple<bool, string>> UpgradePlanAsync(Guid organizationId, OrganizationUpgrade upgrade); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. style: Consider adding XML documentation to describe the purpose of this method, its parameters, and return value |
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
using Bit.Core.Entities; | ||
using Bit.Core.Enums; | ||
using Bit.Core.Models.StaticStore; | ||
|
||
namespace Bit.Core.OrganizationFeatures.OrganizationPlanUpgrade.Interface; | ||
|
||
public interface IOrganizationUpgradeQuery | ||
{ | ||
Plan ExistingPlan(PlanType planType); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. style: Consider adding a return type annotation for clarity |
||
|
||
List<Plan> NewPlans(PlanType planType); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. style: Consider adding a return type annotation for clarity |
||
|
||
Task<Organization> GetOrgById(Guid id); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. style: Method name could be more descriptive, e.g., 'GetOrganizationById' |
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
using Bit.Core.Entities; | ||
using Bit.Core.Models.Business; | ||
using Bit.Core.Models.StaticStore; | ||
|
||
namespace Bit.Core.OrganizationFeatures.OrganizationPlanUpgrade.Interface; | ||
|
||
public interface IValidateUpgradeCommand | ||
{ | ||
void ValidatePlan(Plan newPlan, Plan existingPlan); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. style: Consider adding XML documentation comments to describe the purpose and parameters of each method |
||
Task ValidateSeatsAsync(Organization organization, Plan passwordManagerPlan, OrganizationUpgrade upgrade); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. style: passwordManagerPlan parameter name inconsistent with newPlan in other methods |
||
Task ValidateSmSeatsAsync(Organization organization, Plan newPlan, OrganizationUpgrade upgrade); | ||
Task ValidateServiceAccountAsync(Organization organization, Plan newPlan, OrganizationUpgrade upgrade); | ||
Task ValidateCollectionsAsync(Organization organization, Plan newPlan); | ||
Task ValidateGroupsAsync(Organization organization, Plan newPlan); | ||
Task ValidatePoliciesAsync(Organization organization, Plan newPlan); | ||
Task ValidateSsoAsync(Organization organization, Plan newPlan); | ||
Task ValidateKeyConnectorAsync(Organization organization, Plan newPlan); | ||
Task ValidateResetPasswordAsync(Organization organization, Plan newPlan); | ||
Task ValidateScimAsync(Organization organization, Plan newPlan); | ||
Task ValidateCustomPermissionsAsync(Organization organization, Plan newPlan); | ||
|
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
style: Consider extracting this condition into a separate method for better readability and reusability