mirror of
https://github.com/bitwarden/server
synced 2025-12-10 13:23:27 +00:00
remove hardcoded storage values (#6571)
This commit is contained in:
@@ -75,8 +75,7 @@ public class CloudOrganizationSignUpCommand(
|
|||||||
PlanType = plan!.Type,
|
PlanType = plan!.Type,
|
||||||
Seats = (short)(plan.PasswordManager.BaseSeats + signup.AdditionalSeats),
|
Seats = (short)(plan.PasswordManager.BaseSeats + signup.AdditionalSeats),
|
||||||
MaxCollections = plan.PasswordManager.MaxCollections,
|
MaxCollections = plan.PasswordManager.MaxCollections,
|
||||||
MaxStorageGb = !plan.PasswordManager.BaseStorageGb.HasValue ?
|
MaxStorageGb = (short)(plan.PasswordManager.BaseStorageGb + signup.AdditionalStorageGb),
|
||||||
(short?)null : (short)(plan.PasswordManager.BaseStorageGb.Value + signup.AdditionalStorageGb),
|
|
||||||
UsePolicies = plan.HasPolicies,
|
UsePolicies = plan.HasPolicies,
|
||||||
UseSso = plan.HasSso,
|
UseSso = plan.HasSso,
|
||||||
UseGroups = plan.HasGroups,
|
UseGroups = plan.HasGroups,
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ public class ProviderClientOrganizationSignUpCommand : IProviderClientOrganizati
|
|||||||
PlanType = plan!.Type,
|
PlanType = plan!.Type,
|
||||||
Seats = signup.AdditionalSeats,
|
Seats = signup.AdditionalSeats,
|
||||||
MaxCollections = plan.PasswordManager.MaxCollections,
|
MaxCollections = plan.PasswordManager.MaxCollections,
|
||||||
MaxStorageGb = 1,
|
MaxStorageGb = plan.PasswordManager.BaseStorageGb,
|
||||||
UsePolicies = plan.HasPolicies,
|
UsePolicies = plan.HasPolicies,
|
||||||
UseSso = plan.HasSso,
|
UseSso = plan.HasSso,
|
||||||
UseOrganizationDomains = plan.HasOrganizationDomains,
|
UseOrganizationDomains = plan.HasOrganizationDomains,
|
||||||
|
|||||||
@@ -148,7 +148,7 @@ public class OrganizationService : IOrganizationService
|
|||||||
}
|
}
|
||||||
|
|
||||||
var secret = await BillingHelpers.AdjustStorageAsync(_paymentService, organization, storageAdjustmentGb,
|
var secret = await BillingHelpers.AdjustStorageAsync(_paymentService, organization, storageAdjustmentGb,
|
||||||
plan.PasswordManager.StripeStoragePlanId);
|
plan.PasswordManager.StripeStoragePlanId, plan.PasswordManager.BaseStorageGb);
|
||||||
await ReplaceAndUpdateCacheAsync(organization);
|
await ReplaceAndUpdateCacheAsync(organization);
|
||||||
return secret;
|
return secret;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ public abstract record Plan
|
|||||||
public decimal PremiumAccessOptionPrice { get; init; }
|
public decimal PremiumAccessOptionPrice { get; init; }
|
||||||
public short? MaxSeats { get; init; }
|
public short? MaxSeats { get; init; }
|
||||||
// Storage
|
// Storage
|
||||||
public short? BaseStorageGb { get; init; }
|
public short BaseStorageGb { get; init; }
|
||||||
public bool HasAdditionalStorageOption { get; init; }
|
public bool HasAdditionalStorageOption { get; init; }
|
||||||
public decimal AdditionalStoragePricePerGb { get; init; }
|
public decimal AdditionalStoragePricePerGb { get; init; }
|
||||||
public string StripeStoragePlanId { get; init; }
|
public string StripeStoragePlanId { get; init; }
|
||||||
|
|||||||
@@ -80,6 +80,8 @@ public class CreatePremiumCloudHostedSubscriptionCommand(
|
|||||||
return new BadRequest("Additional storage must be greater than 0.");
|
return new BadRequest("Additional storage must be greater than 0.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var premiumPlan = await pricingClient.GetAvailablePremiumPlan();
|
||||||
|
|
||||||
Customer? customer;
|
Customer? customer;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -107,7 +109,7 @@ public class CreatePremiumCloudHostedSubscriptionCommand(
|
|||||||
|
|
||||||
customer = await ReconcileBillingLocationAsync(customer, billingAddress);
|
customer = await ReconcileBillingLocationAsync(customer, billingAddress);
|
||||||
|
|
||||||
var subscription = await CreateSubscriptionAsync(user.Id, customer, additionalStorageGb > 0 ? additionalStorageGb : null);
|
var subscription = await CreateSubscriptionAsync(user.Id, customer, premiumPlan, additionalStorageGb > 0 ? additionalStorageGb : null);
|
||||||
|
|
||||||
paymentMethod.Switch(
|
paymentMethod.Switch(
|
||||||
tokenized =>
|
tokenized =>
|
||||||
@@ -140,7 +142,7 @@ public class CreatePremiumCloudHostedSubscriptionCommand(
|
|||||||
user.Gateway = GatewayType.Stripe;
|
user.Gateway = GatewayType.Stripe;
|
||||||
user.GatewayCustomerId = customer.Id;
|
user.GatewayCustomerId = customer.Id;
|
||||||
user.GatewaySubscriptionId = subscription.Id;
|
user.GatewaySubscriptionId = subscription.Id;
|
||||||
user.MaxStorageGb = (short)(1 + additionalStorageGb);
|
user.MaxStorageGb = (short)(premiumPlan.Storage.Provided + additionalStorageGb);
|
||||||
user.LicenseKey = CoreHelpers.SecureRandomString(20);
|
user.LicenseKey = CoreHelpers.SecureRandomString(20);
|
||||||
user.RevisionDate = DateTime.UtcNow;
|
user.RevisionDate = DateTime.UtcNow;
|
||||||
|
|
||||||
@@ -304,9 +306,9 @@ public class CreatePremiumCloudHostedSubscriptionCommand(
|
|||||||
private async Task<Subscription> CreateSubscriptionAsync(
|
private async Task<Subscription> CreateSubscriptionAsync(
|
||||||
Guid userId,
|
Guid userId,
|
||||||
Customer customer,
|
Customer customer,
|
||||||
|
Pricing.Premium.Plan premiumPlan,
|
||||||
int? storage)
|
int? storage)
|
||||||
{
|
{
|
||||||
var premiumPlan = await pricingClient.GetAvailablePremiumPlan();
|
|
||||||
|
|
||||||
var subscriptionItemOptionsList = new List<SubscriptionItemOptions>
|
var subscriptionItemOptionsList = new List<SubscriptionItemOptions>
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ public record PlanAdapter : Core.Models.StaticStore.Plan
|
|||||||
_ => true);
|
_ => true);
|
||||||
var baseSeats = GetBaseSeats(plan.Seats);
|
var baseSeats = GetBaseSeats(plan.Seats);
|
||||||
var maxSeats = GetMaxSeats(plan.Seats);
|
var maxSeats = GetMaxSeats(plan.Seats);
|
||||||
var baseStorageGb = (short?)plan.Storage?.Provided;
|
var baseStorageGb = (short)(plan.Storage?.Provided ?? 0);
|
||||||
var hasAdditionalStorageOption = plan.Storage != null;
|
var hasAdditionalStorageOption = plan.Storage != null;
|
||||||
var additionalStoragePricePerGb = plan.Storage?.Price ?? 0;
|
var additionalStoragePricePerGb = plan.Storage?.Price ?? 0;
|
||||||
var stripeStoragePlanId = plan.Storage?.StripePriceId;
|
var stripeStoragePlanId = plan.Storage?.StripePriceId;
|
||||||
|
|||||||
@@ -4,4 +4,5 @@ public class Purchasable
|
|||||||
{
|
{
|
||||||
public string StripePriceId { get; init; } = null!;
|
public string StripePriceId { get; init; } = null!;
|
||||||
public decimal Price { get; init; }
|
public decimal Price { get; init; }
|
||||||
|
public int Provided { get; init; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -186,6 +186,6 @@ public class PricingClient(
|
|||||||
Available = true,
|
Available = true,
|
||||||
LegacyYear = null,
|
LegacyYear = null,
|
||||||
Seat = new Purchasable { Price = 10M, StripePriceId = StripeConstants.Prices.PremiumAnnually },
|
Seat = new Purchasable { Price = 10M, StripePriceId = StripeConstants.Prices.PremiumAnnually },
|
||||||
Storage = new Purchasable { Price = 4M, StripePriceId = StripeConstants.Prices.StoragePlanPersonal }
|
Storage = new Purchasable { Price = 4M, StripePriceId = StripeConstants.Prices.StoragePlanPersonal, Provided = 1 }
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -101,7 +101,9 @@ public class PremiumUserBillingService(
|
|||||||
*/
|
*/
|
||||||
customer = await ReconcileBillingLocationAsync(customer, customerSetup.TaxInformation);
|
customer = await ReconcileBillingLocationAsync(customer, customerSetup.TaxInformation);
|
||||||
|
|
||||||
var subscription = await CreateSubscriptionAsync(user.Id, customer, storage);
|
var premiumPlan = await pricingClient.GetAvailablePremiumPlan();
|
||||||
|
|
||||||
|
var subscription = await CreateSubscriptionAsync(user.Id, customer, premiumPlan, storage);
|
||||||
|
|
||||||
switch (customerSetup.TokenizedPaymentSource)
|
switch (customerSetup.TokenizedPaymentSource)
|
||||||
{
|
{
|
||||||
@@ -119,6 +121,7 @@ public class PremiumUserBillingService(
|
|||||||
user.Gateway = GatewayType.Stripe;
|
user.Gateway = GatewayType.Stripe;
|
||||||
user.GatewayCustomerId = customer.Id;
|
user.GatewayCustomerId = customer.Id;
|
||||||
user.GatewaySubscriptionId = subscription.Id;
|
user.GatewaySubscriptionId = subscription.Id;
|
||||||
|
user.MaxStorageGb = (short)(premiumPlan.Storage.Provided + (storage ?? 0));
|
||||||
|
|
||||||
await userRepository.ReplaceAsync(user);
|
await userRepository.ReplaceAsync(user);
|
||||||
}
|
}
|
||||||
@@ -301,9 +304,9 @@ public class PremiumUserBillingService(
|
|||||||
private async Task<Subscription> CreateSubscriptionAsync(
|
private async Task<Subscription> CreateSubscriptionAsync(
|
||||||
Guid userId,
|
Guid userId,
|
||||||
Customer customer,
|
Customer customer,
|
||||||
|
Pricing.Premium.Plan premiumPlan,
|
||||||
int? storage)
|
int? storage)
|
||||||
{
|
{
|
||||||
var premiumPlan = await pricingClient.GetAvailablePremiumPlan();
|
|
||||||
|
|
||||||
var subscriptionItemOptionsList = new List<SubscriptionItemOptions>
|
var subscriptionItemOptionsList = new List<SubscriptionItemOptions>
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -299,7 +299,7 @@ public class CompleteSubscriptionUpdate : SubscriptionUpdate
|
|||||||
? organization.SmServiceAccounts - plan.SecretsManager.BaseServiceAccount
|
? organization.SmServiceAccounts - plan.SecretsManager.BaseServiceAccount
|
||||||
: 0,
|
: 0,
|
||||||
PurchasedAdditionalStorage = organization.MaxStorageGb.HasValue
|
PurchasedAdditionalStorage = organization.MaxStorageGb.HasValue
|
||||||
? organization.MaxStorageGb.Value - (plan.PasswordManager.BaseStorageGb ?? 0) :
|
? organization.MaxStorageGb.Value - plan.PasswordManager.BaseStorageGb :
|
||||||
0
|
0
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -254,9 +254,7 @@ public class UpgradeOrganizationPlanCommand : IUpgradeOrganizationPlanCommand
|
|||||||
organization.UseApi = newPlan.HasApi;
|
organization.UseApi = newPlan.HasApi;
|
||||||
organization.SelfHost = newPlan.HasSelfHost;
|
organization.SelfHost = newPlan.HasSelfHost;
|
||||||
organization.UsePolicies = newPlan.HasPolicies;
|
organization.UsePolicies = newPlan.HasPolicies;
|
||||||
organization.MaxStorageGb = !newPlan.PasswordManager.BaseStorageGb.HasValue
|
organization.MaxStorageGb = (short)(newPlan.PasswordManager.BaseStorageGb + upgrade.AdditionalStorageGb);
|
||||||
? (short?)null
|
|
||||||
: (short)(newPlan.PasswordManager.BaseStorageGb.Value + upgrade.AdditionalStorageGb);
|
|
||||||
organization.UseGroups = newPlan.HasGroups;
|
organization.UseGroups = newPlan.HasGroups;
|
||||||
organization.UseDirectory = newPlan.HasDirectory;
|
organization.UseDirectory = newPlan.HasDirectory;
|
||||||
organization.UseEvents = newPlan.HasEvents;
|
organization.UseEvents = newPlan.HasEvents;
|
||||||
|
|||||||
@@ -904,7 +904,6 @@ public class UserService : UserManager<User>, IUserService
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
user.MaxStorageGb = (short)(1 + additionalStorageGb);
|
|
||||||
user.LicenseKey = CoreHelpers.SecureRandomString(20);
|
user.LicenseKey = CoreHelpers.SecureRandomString(20);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -977,7 +976,8 @@ public class UserService : UserManager<User>, IUserService
|
|||||||
|
|
||||||
var premiumPlan = await _pricingClient.GetAvailablePremiumPlan();
|
var premiumPlan = await _pricingClient.GetAvailablePremiumPlan();
|
||||||
|
|
||||||
var secret = await BillingHelpers.AdjustStorageAsync(_paymentService, user, storageAdjustmentGb, premiumPlan.Storage.StripePriceId);
|
var baseStorageGb = (short)premiumPlan.Storage.Provided;
|
||||||
|
var secret = await BillingHelpers.AdjustStorageAsync(_paymentService, user, storageAdjustmentGb, premiumPlan.Storage.StripePriceId, baseStorageGb);
|
||||||
await SaveUserAsync(user);
|
await SaveUserAsync(user);
|
||||||
return secret;
|
return secret;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ namespace Bit.Core.Utilities;
|
|||||||
public static class BillingHelpers
|
public static class BillingHelpers
|
||||||
{
|
{
|
||||||
internal static async Task<string> AdjustStorageAsync(IPaymentService paymentService, IStorableSubscriber storableSubscriber,
|
internal static async Task<string> AdjustStorageAsync(IPaymentService paymentService, IStorableSubscriber storableSubscriber,
|
||||||
short storageAdjustmentGb, string storagePlanId)
|
short storageAdjustmentGb, string storagePlanId, short baseStorageGb)
|
||||||
{
|
{
|
||||||
if (storableSubscriber == null)
|
if (storableSubscriber == null)
|
||||||
{
|
{
|
||||||
@@ -30,9 +30,9 @@ public static class BillingHelpers
|
|||||||
}
|
}
|
||||||
|
|
||||||
var newStorageGb = (short)(storableSubscriber.MaxStorageGb.Value + storageAdjustmentGb);
|
var newStorageGb = (short)(storableSubscriber.MaxStorageGb.Value + storageAdjustmentGb);
|
||||||
if (newStorageGb < 1)
|
if (newStorageGb < baseStorageGb)
|
||||||
{
|
{
|
||||||
newStorageGb = 1;
|
newStorageGb = baseStorageGb;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (newStorageGb > 100)
|
if (newStorageGb > 100)
|
||||||
@@ -48,7 +48,7 @@ public static class BillingHelpers
|
|||||||
"Delete some stored data first.");
|
"Delete some stored data first.");
|
||||||
}
|
}
|
||||||
|
|
||||||
var additionalStorage = newStorageGb - 1;
|
var additionalStorage = newStorageGb - baseStorageGb;
|
||||||
var paymentIntentClientSecret = await paymentService.AdjustStorageAsync(storableSubscriber,
|
var paymentIntentClientSecret = await paymentService.AdjustStorageAsync(storableSubscriber,
|
||||||
additionalStorage, storagePlanId);
|
additionalStorage, storagePlanId);
|
||||||
storableSubscriber.MaxStorageGb = newStorageGb;
|
storableSubscriber.MaxStorageGb = newStorageGb;
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ public class CreatePremiumCloudHostedSubscriptionCommandTests
|
|||||||
Available = true,
|
Available = true,
|
||||||
LegacyYear = null,
|
LegacyYear = null,
|
||||||
Seat = new PremiumPurchasable { Price = 10M, StripePriceId = StripeConstants.Prices.PremiumAnnually },
|
Seat = new PremiumPurchasable { Price = 10M, StripePriceId = StripeConstants.Prices.PremiumAnnually },
|
||||||
Storage = new PremiumPurchasable { Price = 4M, StripePriceId = StripeConstants.Prices.StoragePlanPersonal }
|
Storage = new PremiumPurchasable { Price = 4M, StripePriceId = StripeConstants.Prices.StoragePlanPersonal, Provided = 1 }
|
||||||
};
|
};
|
||||||
_pricingClient.GetAvailablePremiumPlan().Returns(premiumPlan);
|
_pricingClient.GetAvailablePremiumPlan().Returns(premiumPlan);
|
||||||
|
|
||||||
@@ -720,4 +720,63 @@ public class CreatePremiumCloudHostedSubscriptionCommandTests
|
|||||||
await _stripeAdapter.DidNotReceive().SubscriptionCreateAsync(Arg.Any<SubscriptionCreateOptions>());
|
await _stripeAdapter.DidNotReceive().SubscriptionCreateAsync(Arg.Any<SubscriptionCreateOptions>());
|
||||||
await _userService.DidNotReceive().SaveUserAsync(Arg.Any<User>());
|
await _userService.DidNotReceive().SaveUserAsync(Arg.Any<User>());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task Run_WithAdditionalStorage_SetsCorrectMaxStorageGb(
|
||||||
|
User user,
|
||||||
|
TokenizedPaymentMethod paymentMethod,
|
||||||
|
BillingAddress billingAddress)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
user.Premium = false;
|
||||||
|
user.GatewayCustomerId = null;
|
||||||
|
user.Email = "test@example.com";
|
||||||
|
paymentMethod.Type = TokenizablePaymentMethodType.Card;
|
||||||
|
paymentMethod.Token = "card_token_123";
|
||||||
|
billingAddress.Country = "US";
|
||||||
|
billingAddress.PostalCode = "12345";
|
||||||
|
const short additionalStorage = 2;
|
||||||
|
|
||||||
|
// Setup premium plan with 5GB provided storage
|
||||||
|
var premiumPlan = new PremiumPlan
|
||||||
|
{
|
||||||
|
Name = "Premium",
|
||||||
|
Available = true,
|
||||||
|
LegacyYear = null,
|
||||||
|
Seat = new PremiumPurchasable { Price = 10M, StripePriceId = StripeConstants.Prices.PremiumAnnually },
|
||||||
|
Storage = new PremiumPurchasable { Price = 4M, StripePriceId = StripeConstants.Prices.StoragePlanPersonal, Provided = 1 }
|
||||||
|
};
|
||||||
|
_pricingClient.GetAvailablePremiumPlan().Returns(premiumPlan);
|
||||||
|
|
||||||
|
var mockCustomer = Substitute.For<StripeCustomer>();
|
||||||
|
mockCustomer.Id = "cust_123";
|
||||||
|
mockCustomer.Address = new Address { Country = "US", PostalCode = "12345" };
|
||||||
|
mockCustomer.Metadata = new Dictionary<string, string>();
|
||||||
|
|
||||||
|
var mockSubscription = Substitute.For<StripeSubscription>();
|
||||||
|
mockSubscription.Id = "sub_123";
|
||||||
|
mockSubscription.Status = "active";
|
||||||
|
mockSubscription.Items = new StripeList<SubscriptionItem>
|
||||||
|
{
|
||||||
|
Data =
|
||||||
|
[
|
||||||
|
new SubscriptionItem
|
||||||
|
{
|
||||||
|
CurrentPeriodEnd = DateTime.UtcNow.AddDays(30)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
_stripeAdapter.CustomerCreateAsync(Arg.Any<CustomerCreateOptions>()).Returns(mockCustomer);
|
||||||
|
_stripeAdapter.SubscriptionCreateAsync(Arg.Any<SubscriptionCreateOptions>()).Returns(mockSubscription);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _command.Run(user, paymentMethod, billingAddress, additionalStorage);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(result.IsT0);
|
||||||
|
Assert.Equal((short)3, user.MaxStorageGb); // 1 (provided) + 2 (additional) = 3
|
||||||
|
await _userService.Received(1).SaveUserAsync(user);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user