mirror of
https://github.com/bitwarden/server
synced 2025-12-06 00:03:34 +00:00
remove hardcoded storage values (#6571)
This commit is contained in:
@@ -75,8 +75,7 @@ public class CloudOrganizationSignUpCommand(
|
||||
PlanType = plan!.Type,
|
||||
Seats = (short)(plan.PasswordManager.BaseSeats + signup.AdditionalSeats),
|
||||
MaxCollections = plan.PasswordManager.MaxCollections,
|
||||
MaxStorageGb = !plan.PasswordManager.BaseStorageGb.HasValue ?
|
||||
(short?)null : (short)(plan.PasswordManager.BaseStorageGb.Value + signup.AdditionalStorageGb),
|
||||
MaxStorageGb = (short)(plan.PasswordManager.BaseStorageGb + signup.AdditionalStorageGb),
|
||||
UsePolicies = plan.HasPolicies,
|
||||
UseSso = plan.HasSso,
|
||||
UseGroups = plan.HasGroups,
|
||||
|
||||
@@ -73,7 +73,7 @@ public class ProviderClientOrganizationSignUpCommand : IProviderClientOrganizati
|
||||
PlanType = plan!.Type,
|
||||
Seats = signup.AdditionalSeats,
|
||||
MaxCollections = plan.PasswordManager.MaxCollections,
|
||||
MaxStorageGb = 1,
|
||||
MaxStorageGb = plan.PasswordManager.BaseStorageGb,
|
||||
UsePolicies = plan.HasPolicies,
|
||||
UseSso = plan.HasSso,
|
||||
UseOrganizationDomains = plan.HasOrganizationDomains,
|
||||
|
||||
@@ -148,7 +148,7 @@ public class OrganizationService : IOrganizationService
|
||||
}
|
||||
|
||||
var secret = await BillingHelpers.AdjustStorageAsync(_paymentService, organization, storageAdjustmentGb,
|
||||
plan.PasswordManager.StripeStoragePlanId);
|
||||
plan.PasswordManager.StripeStoragePlanId, plan.PasswordManager.BaseStorageGb);
|
||||
await ReplaceAndUpdateCacheAsync(organization);
|
||||
return secret;
|
||||
}
|
||||
|
||||
@@ -97,7 +97,7 @@ public abstract record Plan
|
||||
public decimal PremiumAccessOptionPrice { get; init; }
|
||||
public short? MaxSeats { get; init; }
|
||||
// Storage
|
||||
public short? BaseStorageGb { get; init; }
|
||||
public short BaseStorageGb { get; init; }
|
||||
public bool HasAdditionalStorageOption { get; init; }
|
||||
public decimal AdditionalStoragePricePerGb { get; init; }
|
||||
public string StripeStoragePlanId { get; init; }
|
||||
|
||||
@@ -80,6 +80,8 @@ public class CreatePremiumCloudHostedSubscriptionCommand(
|
||||
return new BadRequest("Additional storage must be greater than 0.");
|
||||
}
|
||||
|
||||
var premiumPlan = await pricingClient.GetAvailablePremiumPlan();
|
||||
|
||||
Customer? customer;
|
||||
|
||||
/*
|
||||
@@ -107,7 +109,7 @@ public class CreatePremiumCloudHostedSubscriptionCommand(
|
||||
|
||||
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(
|
||||
tokenized =>
|
||||
@@ -140,7 +142,7 @@ public class CreatePremiumCloudHostedSubscriptionCommand(
|
||||
user.Gateway = GatewayType.Stripe;
|
||||
user.GatewayCustomerId = customer.Id;
|
||||
user.GatewaySubscriptionId = subscription.Id;
|
||||
user.MaxStorageGb = (short)(1 + additionalStorageGb);
|
||||
user.MaxStorageGb = (short)(premiumPlan.Storage.Provided + additionalStorageGb);
|
||||
user.LicenseKey = CoreHelpers.SecureRandomString(20);
|
||||
user.RevisionDate = DateTime.UtcNow;
|
||||
|
||||
@@ -304,9 +306,9 @@ public class CreatePremiumCloudHostedSubscriptionCommand(
|
||||
private async Task<Subscription> CreateSubscriptionAsync(
|
||||
Guid userId,
|
||||
Customer customer,
|
||||
Pricing.Premium.Plan premiumPlan,
|
||||
int? storage)
|
||||
{
|
||||
var premiumPlan = await pricingClient.GetAvailablePremiumPlan();
|
||||
|
||||
var subscriptionItemOptionsList = new List<SubscriptionItemOptions>
|
||||
{
|
||||
|
||||
@@ -99,7 +99,7 @@ public record PlanAdapter : Core.Models.StaticStore.Plan
|
||||
_ => true);
|
||||
var baseSeats = GetBaseSeats(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 additionalStoragePricePerGb = plan.Storage?.Price ?? 0;
|
||||
var stripeStoragePlanId = plan.Storage?.StripePriceId;
|
||||
|
||||
@@ -4,4 +4,5 @@ public class Purchasable
|
||||
{
|
||||
public string StripePriceId { get; init; } = null!;
|
||||
public decimal Price { get; init; }
|
||||
public int Provided { get; init; }
|
||||
}
|
||||
|
||||
@@ -186,6 +186,6 @@ public class PricingClient(
|
||||
Available = true,
|
||||
LegacyYear = null,
|
||||
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);
|
||||
|
||||
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)
|
||||
{
|
||||
@@ -119,6 +121,7 @@ public class PremiumUserBillingService(
|
||||
user.Gateway = GatewayType.Stripe;
|
||||
user.GatewayCustomerId = customer.Id;
|
||||
user.GatewaySubscriptionId = subscription.Id;
|
||||
user.MaxStorageGb = (short)(premiumPlan.Storage.Provided + (storage ?? 0));
|
||||
|
||||
await userRepository.ReplaceAsync(user);
|
||||
}
|
||||
@@ -301,9 +304,9 @@ public class PremiumUserBillingService(
|
||||
private async Task<Subscription> CreateSubscriptionAsync(
|
||||
Guid userId,
|
||||
Customer customer,
|
||||
Pricing.Premium.Plan premiumPlan,
|
||||
int? storage)
|
||||
{
|
||||
var premiumPlan = await pricingClient.GetAvailablePremiumPlan();
|
||||
|
||||
var subscriptionItemOptionsList = new List<SubscriptionItemOptions>
|
||||
{
|
||||
|
||||
@@ -299,7 +299,7 @@ public class CompleteSubscriptionUpdate : SubscriptionUpdate
|
||||
? organization.SmServiceAccounts - plan.SecretsManager.BaseServiceAccount
|
||||
: 0,
|
||||
PurchasedAdditionalStorage = organization.MaxStorageGb.HasValue
|
||||
? organization.MaxStorageGb.Value - (plan.PasswordManager.BaseStorageGb ?? 0) :
|
||||
? organization.MaxStorageGb.Value - plan.PasswordManager.BaseStorageGb :
|
||||
0
|
||||
};
|
||||
}
|
||||
|
||||
@@ -254,9 +254,7 @@ public class UpgradeOrganizationPlanCommand : IUpgradeOrganizationPlanCommand
|
||||
organization.UseApi = newPlan.HasApi;
|
||||
organization.SelfHost = newPlan.HasSelfHost;
|
||||
organization.UsePolicies = newPlan.HasPolicies;
|
||||
organization.MaxStorageGb = !newPlan.PasswordManager.BaseStorageGb.HasValue
|
||||
? (short?)null
|
||||
: (short)(newPlan.PasswordManager.BaseStorageGb.Value + upgrade.AdditionalStorageGb);
|
||||
organization.MaxStorageGb = (short)(newPlan.PasswordManager.BaseStorageGb + upgrade.AdditionalStorageGb);
|
||||
organization.UseGroups = newPlan.HasGroups;
|
||||
organization.UseDirectory = newPlan.HasDirectory;
|
||||
organization.UseEvents = newPlan.HasEvents;
|
||||
|
||||
@@ -904,7 +904,6 @@ public class UserService : UserManager<User>, IUserService
|
||||
}
|
||||
else
|
||||
{
|
||||
user.MaxStorageGb = (short)(1 + additionalStorageGb);
|
||||
user.LicenseKey = CoreHelpers.SecureRandomString(20);
|
||||
}
|
||||
|
||||
@@ -977,7 +976,8 @@ public class UserService : UserManager<User>, IUserService
|
||||
|
||||
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);
|
||||
return secret;
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace Bit.Core.Utilities;
|
||||
public static class BillingHelpers
|
||||
{
|
||||
internal static async Task<string> AdjustStorageAsync(IPaymentService paymentService, IStorableSubscriber storableSubscriber,
|
||||
short storageAdjustmentGb, string storagePlanId)
|
||||
short storageAdjustmentGb, string storagePlanId, short baseStorageGb)
|
||||
{
|
||||
if (storableSubscriber == null)
|
||||
{
|
||||
@@ -30,9 +30,9 @@ public static class BillingHelpers
|
||||
}
|
||||
|
||||
var newStorageGb = (short)(storableSubscriber.MaxStorageGb.Value + storageAdjustmentGb);
|
||||
if (newStorageGb < 1)
|
||||
if (newStorageGb < baseStorageGb)
|
||||
{
|
||||
newStorageGb = 1;
|
||||
newStorageGb = baseStorageGb;
|
||||
}
|
||||
|
||||
if (newStorageGb > 100)
|
||||
@@ -48,7 +48,7 @@ public static class BillingHelpers
|
||||
"Delete some stored data first.");
|
||||
}
|
||||
|
||||
var additionalStorage = newStorageGb - 1;
|
||||
var additionalStorage = newStorageGb - baseStorageGb;
|
||||
var paymentIntentClientSecret = await paymentService.AdjustStorageAsync(storableSubscriber,
|
||||
additionalStorage, storagePlanId);
|
||||
storableSubscriber.MaxStorageGb = newStorageGb;
|
||||
|
||||
@@ -53,7 +53,7 @@ public class CreatePremiumCloudHostedSubscriptionCommandTests
|
||||
Available = true,
|
||||
LegacyYear = null,
|
||||
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);
|
||||
|
||||
@@ -720,4 +720,63 @@ public class CreatePremiumCloudHostedSubscriptionCommandTests
|
||||
await _stripeAdapter.DidNotReceive().SubscriptionCreateAsync(Arg.Any<SubscriptionCreateOptions>());
|
||||
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