-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Expand file tree
/
Copy pathUpgradeOrganizationPlanVNextCommand.cs
More file actions
172 lines (146 loc) · 6.56 KB
/
UpgradeOrganizationPlanVNextCommand.cs
File metadata and controls
172 lines (146 loc) · 6.56 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.OrganizationFeatures.Organizations;
using Bit.Core.Billing.Commands;
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.KeyManagement.Models.Data;
using Bit.Core.Models.Business;
using Bit.Core.Models.StaticStore;
using Bit.Core.Services;
using Microsoft.Extensions.Logging;
using OneOf.Types;
namespace Bit.Core.Billing.Organizations.Commands;
/// <summary>
/// Upgrades an organization's subscription plan by updating its Stripe subscription
/// and persisting the corresponding feature and configuration changes to the database.
/// </summary>
public interface IUpgradeOrganizationPlanVNextCommand
{
/// <summary>
/// Upgrades the <paramref name="organization"/> to the specified <paramref name="plan"/>
/// by applying subscription changes through <see cref="IUpdateOrganizationSubscriptionCommand"/>
/// and updating the organization's features, limits, and encryption keys.
/// </summary>
/// <param name="organization">The organization to upgrade.</param>
/// <param name="plan">The target plan to upgrade to.</param>
/// <param name="keys">Optional public key encryption key pair data to set during the upgrade.</param>
/// <returns>
/// A <see cref="BillingCommandResult{T}"/> containing <see cref="None"/> on success,
/// or an error result if the subscription update or feature persistence fails.
/// </returns>
Task<BillingCommandResult<None>> Run(
Organization organization,
Plan plan,
PublicKeyEncryptionKeyPairData? keys);
}
public class UpgradeOrganizationPlanVNextCommand(
ILogger<UpgradeOrganizationPlanVNextCommand> logger,
IOrganizationBillingService organizationBillingService,
IOrganizationService organizationService,
IPricingClient pricingClient,
IUpdateOrganizationSubscriptionCommand updateOrganizationSubscriptionCommand) : BaseBillingCommand<UpgradeOrganizationPlanVNextCommand>(logger), IUpgradeOrganizationPlanVNextCommand
{
protected override Conflict DefaultConflict => new("We had a problem upgrading your plan. Please contact support for assistance.");
public Task<BillingCommandResult<None>> Run(
Organization organization,
Plan plan,
PublicKeyEncryptionKeyPairData? keys) => HandleAsync(async () =>
{
var currentPlan = await pricingClient.GetPlanOrThrow(organization.PlanType);
if (currentPlan.UpgradeSortOrder == plan.UpgradeSortOrder)
{
return new BadRequest("Your organization is already on this plan.");
}
if (currentPlan.UpgradeSortOrder > plan.UpgradeSortOrder)
{
return new BadRequest("You can't downgrade your organization's plan.");
}
if (string.IsNullOrEmpty(organization.GatewayCustomerId))
{
return new Conflict($"Organization's ({organization.Id}) Stripe customer should already have been created");
}
// Upgrade from Free
if (currentPlan.Type == PlanType.Free && organization is
{
GatewaySubscriptionId: null,
Seats: not null
})
{
var sale = OrganizationSale.From(organization, new OrganizationUpgrade
{
Plan = plan.Type,
AdditionalSeats = organization.Seats ?? 0,
UseSecretsManager = organization.UseSecretsManager,
AdditionalSmSeats = organization.UseSecretsManager ? organization.SmSeats : null,
}, null);
await organizationBillingService.Finalize(sale);
if (plan.HasNonSeatBasedPasswordManagerPlan())
{
organization.Seats = plan.PasswordManager.BaseSeats;
}
organization.MaxStorageGb = plan.PasswordManager.BaseStorageGb;
if (organization.UseSecretsManager)
{
organization.SmServiceAccounts = plan.SecretsManager.BaseServiceAccount;
}
await UpdateOrganizationFeaturesAsync(organization, plan, keys);
return new None();
}
var builder = OrganizationSubscriptionChangeSet.Builder(currentPlan);
builder.ChangePasswordManagerPrice(plan);
if (organization.MaxStorageGb > currentPlan.PasswordManager.BaseStorageGb)
{
builder.ChangeStoragePrice(plan);
}
if (organization.UseSecretsManager)
{
builder.ChangeSecretsManagerSeatPrice(plan);
if (organization.SmServiceAccounts > currentPlan.SecretsManager.BaseServiceAccount)
{
builder.ChangeServiceAccountPrice(plan);
}
}
var changeSet = builder.Build();
var result = await updateOrganizationSubscriptionCommand.Run(organization, changeSet);
if (!result.Success)
{
return result.Map(_ => new None());
}
await UpdateOrganizationFeaturesAsync(organization, plan, keys);
return result.Map(_ => new None());
});
private async Task UpdateOrganizationFeaturesAsync(
Organization organization,
Plan plan,
PublicKeyEncryptionKeyPairData? keys)
{
organization.Plan = plan.Name;
organization.PlanType = plan.Type;
organization.MaxCollections = plan.PasswordManager.MaxCollections;
organization.UsePolicies = plan.HasPolicies;
organization.UseSso = plan.HasSso;
organization.UseKeyConnector = plan.HasKeyConnector;
organization.UseScim = plan.HasScim;
organization.UseGroups = plan.HasGroups;
organization.UseDirectory = plan.HasDirectory;
organization.UseEvents = plan.HasEvents;
organization.UseTotp = plan.HasTotp;
organization.Use2fa = plan.Has2fa;
organization.UseApi = plan.HasApi;
organization.UseResetPassword = plan.HasResetPassword;
organization.SelfHost = plan.HasSelfHost;
organization.UsersGetPremium = plan.UsersGetPremium;
organization.UseCustomPermissions = plan.HasCustomPermissions;
organization.UseOrganizationDomains = plan.HasOrganizationDomains;
organization.UseAutomaticUserConfirmation = plan.AutomaticUserConfirmation;
organization.UseMyItems = plan.HasMyItems;
organization.UseInviteLinks = plan.HasInviteLinks;
if (keys != null)
{
organization.BackfillPublicPrivateKeys(keys);
}
await organizationService.ReplaceAndUpdateCacheAsync(organization);
}
}