diff --git a/src/Api/Controllers/AccountsController.cs b/src/Api/Controllers/AccountsController.cs index 997c04f88d..b16e5ea222 100644 --- a/src/Api/Controllers/AccountsController.cs +++ b/src/Api/Controllers/AccountsController.cs @@ -369,7 +369,7 @@ namespace Bit.Api.Controllers throw new NotFoundException(); } - return new BillingResponseModel(billingInfo); + return new BillingResponseModel(user, billingInfo); } [HttpPut("payment")] diff --git a/src/Core/Models/Api/Request/Organizations/OrganizationCreateRequestModel.cs b/src/Core/Models/Api/Request/Organizations/OrganizationCreateRequestModel.cs index 61f6a8f32d..67fe7aede2 100644 --- a/src/Core/Models/Api/Request/Organizations/OrganizationCreateRequestModel.cs +++ b/src/Core/Models/Api/Request/Organizations/OrganizationCreateRequestModel.cs @@ -23,6 +23,8 @@ namespace Bit.Core.Models.Api public string PaymentToken { get; set; } [Range(0, double.MaxValue)] public short AdditionalSeats { get; set; } + [Range(0, 99)] + public short? AdditionalStorageGb { get; set; } public virtual OrganizationSignup ToOrganizationSignup(User user) { @@ -34,6 +36,7 @@ namespace Bit.Core.Models.Api Plan = PlanType, PaymentToken = PaymentToken, AdditionalSeats = AdditionalSeats, + AdditionalStorageGb = AdditionalStorageGb.GetValueOrDefault(0), BillingEmail = BillingEmail, BusinessName = BusinessName }; diff --git a/src/Core/Models/Api/Response/BillingResponseModel.cs b/src/Core/Models/Api/Response/BillingResponseModel.cs index c018c6c40c..06848db5c9 100644 --- a/src/Core/Models/Api/Response/BillingResponseModel.cs +++ b/src/Core/Models/Api/Response/BillingResponseModel.cs @@ -3,20 +3,27 @@ using System.Linq; using System.Collections.Generic; using Bit.Core.Models.Business; using Stripe; +using Bit.Core.Models.Table; namespace Bit.Core.Models.Api { public class BillingResponseModel : ResponseModel { - public BillingResponseModel(BillingInfo billing) + public BillingResponseModel(IStorable storable, BillingInfo billing) : base("billing") { PaymentSource = billing.PaymentSource != null ? new BillingSource(billing.PaymentSource) : null; Subscription = billing.Subscription != null ? new BillingSubscription(billing.Subscription) : null; Charges = billing.Charges.Select(c => new BillingCharge(c)); UpcomingInvoice = billing.UpcomingInvoice != null ? new BillingInvoice(billing.UpcomingInvoice) : null; + StorageName = storable.Storage.HasValue ? Utilities.CoreHelpers.ReadableBytesSize(storable.Storage.Value) : null; + StorageGb = storable.Storage.HasValue ? Math.Round(storable.Storage.Value / 1073741824D, 2) : 0; // 1 GB + MaxStorageGb = storable.MaxStorageGb; } + public string StorageName { get; set; } + public double? StorageGb { get; set; } + public short? MaxStorageGb { get; set; } public BillingSource PaymentSource { get; set; } public BillingSubscription Subscription { get; set; } public BillingInvoice UpcomingInvoice { get; set; } diff --git a/src/Core/Models/Api/Response/OrganizationResponseModel.cs b/src/Core/Models/Api/Response/OrganizationResponseModel.cs index 71522f0979..533fd2d5d0 100644 --- a/src/Core/Models/Api/Response/OrganizationResponseModel.cs +++ b/src/Core/Models/Api/Response/OrganizationResponseModel.cs @@ -26,6 +26,7 @@ namespace Bit.Core.Models.Api MaxCollections = organization.MaxCollections; UseGroups = organization.UseGroups; UseDirectory = organization.UseDirectory; + UseTotp = organization.UseTotp; } public string Id { get; set; } @@ -38,6 +39,7 @@ namespace Bit.Core.Models.Api public short? MaxCollections { get; set; } public bool UseGroups { get; set; } public bool UseDirectory { get; set; } + public bool UseTotp { get; set; } } public class OrganizationBillingResponseModel : OrganizationResponseModel @@ -49,8 +51,15 @@ namespace Bit.Core.Models.Api Subscription = billing.Subscription != null ? new BillingSubscription(billing.Subscription) : null; Charges = billing.Charges.Select(c => new BillingCharge(c)); UpcomingInvoice = billing.UpcomingInvoice != null ? new BillingInvoice(billing.UpcomingInvoice) : null; + StorageName = organization.Storage.HasValue ? + Utilities.CoreHelpers.ReadableBytesSize(organization.Storage.Value) : null; + StorageGb = organization.Storage.HasValue ? Math.Round(organization.Storage.Value / 1073741824D) : 0; // 1 GB + MaxStorageGb = organization.MaxStorageGb; } + public string StorageName { get; set; } + public double? StorageGb { get; set; } + public short? MaxStorageGb { get; set; } public BillingSource PaymentSource { get; set; } public BillingSubscription Subscription { get; set; } public BillingInvoice UpcomingInvoice { get; set; } diff --git a/src/Core/Models/Business/OrganizationSignup.cs b/src/Core/Models/Business/OrganizationSignup.cs index 4a65f61829..3a1c8e424f 100644 --- a/src/Core/Models/Business/OrganizationSignup.cs +++ b/src/Core/Models/Business/OrganizationSignup.cs @@ -12,6 +12,7 @@ namespace Bit.Core.Models.Business public string OwnerKey { get; set; } public Enums.PlanType Plan { get; set; } public short AdditionalSeats { get; set; } + public short AdditionalStorageGb { get; set; } public string PaymentToken { get; set; } } } diff --git a/src/Core/Models/StaticStore/Plan.cs b/src/Core/Models/StaticStore/Plan.cs index 93d94bdbfd..fa5ebf44f7 100644 --- a/src/Core/Models/StaticStore/Plan.cs +++ b/src/Core/Models/StaticStore/Plan.cs @@ -7,6 +7,7 @@ namespace Bit.Core.Models.StaticStore public string Name { get; set; } public string StripePlanId { get; set; } public string StripeSeatPlanId { get; set; } + public string StripStoragePlanId { get; set; } public PlanType Type { get; set; } public short BaseSeats { get; set; } public bool CanBuyAdditionalSeats { get; set; } diff --git a/src/Core/Services/Implementations/OrganizationService.cs b/src/Core/Services/Implementations/OrganizationService.cs index c5207249e6..c94d69a080 100644 --- a/src/Core/Services/Implementations/OrganizationService.cs +++ b/src/Core/Services/Implementations/OrganizationService.cs @@ -334,6 +334,11 @@ namespace Bit.Core.Services StripeCustomer customer = null; StripeSubscription subscription = null; + if(!plan.MaxStorageGb.HasValue && signup.AdditionalStorageGb > 0) + { + throw new BadRequestException("Plan does not allow additional storage."); + } + if(plan.BaseSeats + signup.AdditionalSeats <= 0) { throw new BadRequestException("You do not have any seats!"); @@ -399,6 +404,15 @@ namespace Bit.Core.Services }); } + if(signup.AdditionalStorageGb > 0) + { + subCreateOptions.Items.Add(new StripeSubscriptionItemOption + { + PlanId = plan.StripStoragePlanId, + Quantity = signup.AdditionalStorageGb + }); + } + try { subscription = await subscriptionService.CreateAsync(customer.Id, subCreateOptions); @@ -423,7 +437,8 @@ namespace Bit.Core.Services PlanType = plan.Type, Seats = (short)(plan.BaseSeats + signup.AdditionalSeats), MaxCollections = plan.MaxCollections, - MaxStorageGb = plan.MaxStorageGb, + MaxStorageGb = !plan.MaxStorageGb.HasValue ? + (short?)null : (short)(plan.MaxStorageGb.Value + signup.AdditionalStorageGb), UseGroups = plan.UseGroups, UseDirectory = plan.UseDirectory, UseTotp = plan.UseTotp, diff --git a/src/Core/Utilities/StaticStore.cs b/src/Core/Utilities/StaticStore.cs index 83b8e3a445..e6ba3b928d 100644 --- a/src/Core/Utilities/StaticStore.cs +++ b/src/Core/Utilities/StaticStore.cs @@ -110,6 +110,7 @@ namespace Bit.Core.Utilities Name = "Personal", StripePlanId = "personal-org-annually", StripeSeatPlanId = "personal-org-seat-annually", + StripStoragePlanId = "storage-gb-annually", UpgradeSortOrder = 1, TrialPeriodDays = 7, UseTotp = true, @@ -125,6 +126,7 @@ namespace Bit.Core.Utilities Name = "Teams (Monthly)", StripePlanId = "teams-org-monthly", StripeSeatPlanId = "teams-org-seat-monthly", + StripStoragePlanId = "storage-gb-monthly", UpgradeSortOrder = 2, TrialPeriodDays = 7, UseTotp = true, @@ -140,6 +142,7 @@ namespace Bit.Core.Utilities Name = "Teams (Annually)", StripePlanId = "teams-org-annually", StripeSeatPlanId = "teams-org-seat-annually", + StripStoragePlanId = "storage-gb-annually", UpgradeSortOrder = 2, TrialPeriodDays = 7, UseTotp = true, @@ -155,6 +158,7 @@ namespace Bit.Core.Utilities Name = "Enterprise (Monthly)", StripePlanId = null, StripeSeatPlanId = "enterprise-org-seat-monthly", + StripStoragePlanId = "storage-gb-monthly", UpgradeSortOrder = 3, TrialPeriodDays = 7, UseGroups = true, @@ -172,6 +176,7 @@ namespace Bit.Core.Utilities Name = "Enterprise (Annually)", StripePlanId = null, StripeSeatPlanId = "enterprise-org-seat-annually", + StripStoragePlanId = "storage-gb-annually", UpgradeSortOrder = 3, TrialPeriodDays = 7, UseGroups = true,