diff --git a/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.ts b/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.ts
index b2534e78c5..eb7180f7c8 100644
--- a/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.ts
+++ b/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.ts
@@ -8,7 +8,7 @@ import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-conso
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { OrganizationApiKeyType } from "@bitwarden/common/admin-console/enums";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
-import { BitwardenProductType, PlanType } from "@bitwarden/common/billing/enums";
+import { PlanType } from "@bitwarden/common/billing/enums";
import { OrganizationSubscriptionResponse } from "@bitwarden/common/billing/models/response/organization-subscription.response";
import { BillingSubscriptionItemResponse } from "@bitwarden/common/billing/models/response/subscription.response";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
@@ -74,17 +74,6 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy
.subscribe();
}
- productName(product: BitwardenProductType) {
- switch (product) {
- case BitwardenProductType.PasswordManager:
- return this.i18nService.t("passwordManager");
- case BitwardenProductType.SecretsManager:
- return this.i18nService.t("secretsManager");
- default:
- return this.i18nService.t("passwordManager");
- }
- }
-
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
@@ -98,7 +87,20 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy
this.userOrg = this.organizationService.get(this.organizationId);
if (this.userOrg.canViewSubscription) {
this.sub = await this.organizationApiService.getSubscription(this.organizationId);
- this.lineItems = this.sub?.subscription?.items?.sort(sortSubscriptionItems) ?? [];
+ this.lineItems = this.sub?.subscription?.items;
+ if (this.lineItems && this.lineItems.length) {
+ this.lineItems = this.lineItems
+ .map((item) => {
+ const itemTotalAmount = item.amount * item.quantity;
+ const seatPriceTotal = this.sub.plan?.SecretsManager?.seatPrice * item.quantity;
+ item.productName =
+ itemTotalAmount === seatPriceTotal || item.name.includes("Service Accounts")
+ ? "SecretsManager"
+ : "PasswordManager";
+ return item;
+ })
+ .sort(sortSubscriptionItems);
+ }
}
const apiKeyResponse = await this.organizationApiService.getApiKeyInformation(
@@ -111,6 +113,7 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy
this.showSecretsManagerSubscribe =
this.userOrg.canEditSubscription &&
!this.userOrg.hasProvider &&
+ this.sub?.plan?.SecretsManager &&
!this.userOrg.useSecretsManager &&
!this.subscription?.cancelled &&
!this.subscriptionMarkedForCancel;
@@ -119,7 +122,7 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy
this.userOrg.canEditSubscription &&
this.userOrg.useSecretsManager &&
this.subscription != null &&
- this.sub.secretsManagerPlan?.hasAdditionalSeatsOption &&
+ this.sub.plan?.SecretsManager?.hasAdditionalSeatsOption &&
!this.sub.secretsManagerBeta &&
!this.subscription.cancelled &&
!this.subscriptionMarkedForCancel;
@@ -165,11 +168,11 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy
}
get storageGbPrice() {
- return this.sub.plan.additionalStoragePricePerGb;
+ return this.sub.plan.PasswordManager.additionalStoragePricePerGb;
}
get seatPrice() {
- return this.sub.plan.seatPrice;
+ return this.sub.plan.PasswordManager.seatPrice;
}
get seats() {
@@ -180,13 +183,13 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy
return {
seatCount: this.sub.smSeats,
maxAutoscaleSeats: this.sub.maxAutoscaleSmSeats,
- seatPrice: this.sub.secretsManagerPlan.seatPrice,
+ seatPrice: this.sub.plan.SecretsManager.seatPrice,
maxAutoscaleServiceAccounts: this.sub.maxAutoscaleSmServiceAccounts,
additionalServiceAccounts:
- this.sub.smServiceAccounts - this.sub.secretsManagerPlan.baseServiceAccount,
- interval: this.sub.secretsManagerPlan.isAnnual ? "year" : "month",
- additionalServiceAccountPrice: this.sub.secretsManagerPlan.additionalPricePerServiceAccount,
- baseServiceAccountCount: this.sub.secretsManagerPlan.baseServiceAccount,
+ this.sub.smServiceAccounts - this.sub.plan.SecretsManager.baseServiceAccount,
+ interval: this.sub.plan.isAnnual ? "year" : "month",
+ additionalServiceAccountPrice: this.sub.plan.SecretsManager.additionalPricePerServiceAccount,
+ baseServiceAccountCount: this.sub.plan.SecretsManager.baseServiceAccount,
};
}
@@ -195,7 +198,7 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy
}
get canAdjustSeats() {
- return this.sub.plan.hasAdditionalSeatsOption;
+ return this.sub.plan.PasswordManager.hasAdditionalSeatsOption;
}
get isAdmin() {
@@ -391,7 +394,7 @@ function sortSubscriptionItems(
a: BillingSubscriptionItemResponse,
b: BillingSubscriptionItemResponse
) {
- if (a.bitwardenProduct == b.bitwardenProduct) {
+ if (a.productName == b.productName) {
if (a.addonSubscriptionItem == b.addonSubscriptionItem) {
return 0;
}
@@ -401,5 +404,5 @@ function sortSubscriptionItems(
}
return -1;
}
- return a.bitwardenProduct - b.bitwardenProduct;
+ return a.productName.localeCompare(b.productName);
}
diff --git a/apps/web/src/app/billing/organizations/sm-subscribe-standalone.component.ts b/apps/web/src/app/billing/organizations/sm-subscribe-standalone.component.ts
index 3ca365fb9b..2942a67560 100644
--- a/apps/web/src/app/billing/organizations/sm-subscribe-standalone.component.ts
+++ b/apps/web/src/app/billing/organizations/sm-subscribe-standalone.component.ts
@@ -33,10 +33,10 @@ export class SecretsManagerSubscribeStandaloneComponent {
submit = async () => {
const request = new SecretsManagerSubscribeRequest();
- request.additionalSmSeats = this.plan.hasAdditionalSeatsOption
+ request.additionalSmSeats = this.plan.SecretsManager.hasAdditionalSeatsOption
? this.formGroup.value.userSeats
: 0;
- request.additionalServiceAccounts = this.plan.hasAdditionalServiceAccountOption
+ request.additionalServiceAccounts = this.plan.SecretsManager.hasAdditionalServiceAccountOption
? this.formGroup.value.additionalServiceAccounts
: 0;
diff --git a/apps/web/src/app/billing/shared/sm-subscribe.component.html b/apps/web/src/app/billing/shared/sm-subscribe.component.html
index 62a80c2a99..4c16108a6f 100644
--- a/apps/web/src/app/billing/shared/sm-subscribe.component.html
+++ b/apps/web/src/app/billing/shared/sm-subscribe.component.html
@@ -45,14 +45,14 @@
-
+
{{ "userSeats" | i18n }}
{{ "userSeatsHowManyDesc" | i18n }}
-
+
{{ "additionalServiceAccounts" | i18n }}
diff --git a/apps/web/src/app/billing/shared/sm-subscribe.component.ts b/apps/web/src/app/billing/shared/sm-subscribe.component.ts
index b48809813b..1aa6c1bccb 100644
--- a/apps/web/src/app/billing/shared/sm-subscribe.component.ts
+++ b/apps/web/src/app/billing/shared/sm-subscribe.component.ts
@@ -79,26 +79,26 @@ export class SecretsManagerSubscribeComponent implements OnInit, OnDestroy {
}
get serviceAccountsIncluded() {
- return this.selectedPlan.baseServiceAccount;
+ return this.selectedPlan.SecretsManager.baseServiceAccount;
}
get monthlyCostPerServiceAccount() {
return this.selectedPlan.isAnnual
- ? this.selectedPlan.additionalPricePerServiceAccount / 12
- : this.selectedPlan.additionalPricePerServiceAccount;
+ ? this.selectedPlan.SecretsManager.additionalPricePerServiceAccount / 12
+ : this.selectedPlan.SecretsManager.additionalPricePerServiceAccount;
}
get maxUsers() {
- return this.selectedPlan.maxUsers;
+ return this.selectedPlan.SecretsManager.maxSeats;
}
get maxProjects() {
- return this.selectedPlan.maxProjects;
+ return this.selectedPlan.SecretsManager.maxProjects;
}
get monthlyCostPerUser() {
return this.selectedPlan.isAnnual
- ? this.selectedPlan.seatPrice / 12
- : this.selectedPlan.seatPrice;
+ ? this.selectedPlan.SecretsManager.seatPrice / 12
+ : this.selectedPlan.SecretsManager.seatPrice;
}
}
diff --git a/libs/common/src/admin-console/models/response/organization.response.ts b/libs/common/src/admin-console/models/response/organization.response.ts
index b248c6d0df..3d49796352 100644
--- a/libs/common/src/admin-console/models/response/organization.response.ts
+++ b/libs/common/src/admin-console/models/response/organization.response.ts
@@ -13,7 +13,6 @@ export class OrganizationResponse extends BaseResponse {
businessTaxNumber: string;
billingEmail: string;
plan: PlanResponse;
- secretsManagerPlan: PlanResponse;
planType: PlanType;
seats: number;
maxAutoscaleSeats: number;
@@ -49,10 +48,6 @@ export class OrganizationResponse extends BaseResponse {
const plan = this.getResponseProperty("Plan");
this.plan = plan == null ? null : new PlanResponse(plan);
- const secretsManagerPlan = this.getResponseProperty("SecretsManagerPlan");
- this.secretsManagerPlan =
- secretsManagerPlan == null ? null : new PlanResponse(secretsManagerPlan);
-
this.planType = this.getResponseProperty("PlanType");
this.seats = this.getResponseProperty("Seats");
this.maxAutoscaleSeats = this.getResponseProperty("MaxAutoscaleSeats");
diff --git a/libs/common/src/billing/models/response/plan.response.ts b/libs/common/src/billing/models/response/plan.response.ts
index bb09a9b143..530b7dd877 100644
--- a/libs/common/src/billing/models/response/plan.response.ts
+++ b/libs/common/src/billing/models/response/plan.response.ts
@@ -1,28 +1,16 @@
import { ProductType } from "../../../enums";
import { BaseResponse } from "../../../models/response/base.response";
-import { BitwardenProductType, PlanType } from "../../enums";
+import { PlanType } from "../../enums";
export class PlanResponse extends BaseResponse {
type: PlanType;
product: ProductType;
- bitwardenProduct: BitwardenProductType;
name: string;
isAnnual: boolean;
nameLocalizationKey: string;
descriptionLocalizationKey: string;
canBeUsedByBusiness: boolean;
- baseSeats: number;
- baseStorageGb: number;
- maxCollections: number;
- maxUsers: number;
-
- hasAdditionalSeatsOption: boolean;
- maxAdditionalSeats: number;
- hasAdditionalStorageOption: boolean;
- maxAdditionalStorage: number;
- hasPremiumAccessOption: boolean;
trialPeriodDays: number;
-
hasSelfHost: boolean;
hasPolicies: boolean;
hasGroups: boolean;
@@ -34,29 +22,12 @@ export class PlanResponse extends BaseResponse {
hasSso: boolean;
hasResetPassword: boolean;
usersGetPremium: boolean;
-
upgradeSortOrder: number;
displaySortOrder: number;
legacyYear: number;
disabled: boolean;
-
- stripePlanId: string;
- stripeSeatPlanId: string;
- stripeStoragePlanId: string;
- stripePremiumAccessPlanId: string;
- basePrice: number;
- seatPrice: number;
- additionalStoragePricePerGb: number;
- premiumAccessOptionPrice: number;
-
- // SM only
- additionalPricePerServiceAccount: number;
- baseServiceAccount: number;
- maxServiceAccount: number;
- hasAdditionalServiceAccountOption: boolean;
- maxProjects: number;
- maxAdditionalServiceAccounts: number;
- stripeServiceAccountPlanId: string;
+ PasswordManager: PasswordManagerPlanFeaturesResponse;
+ SecretsManager: SecretsManagerPlanFeaturesResponse;
constructor(response: any) {
super(response);
@@ -67,15 +38,6 @@ export class PlanResponse extends BaseResponse {
this.nameLocalizationKey = this.getResponseProperty("NameLocalizationKey");
this.descriptionLocalizationKey = this.getResponseProperty("DescriptionLocalizationKey");
this.canBeUsedByBusiness = this.getResponseProperty("CanBeUsedByBusiness");
- this.baseSeats = this.getResponseProperty("BaseSeats");
- this.baseStorageGb = this.getResponseProperty("BaseStorageGb");
- this.maxCollections = this.getResponseProperty("MaxCollections");
- this.maxUsers = this.getResponseProperty("MaxUsers");
- this.hasAdditionalSeatsOption = this.getResponseProperty("HasAdditionalSeatsOption");
- this.maxAdditionalSeats = this.getResponseProperty("MaxAdditionalSeats");
- this.hasAdditionalStorageOption = this.getResponseProperty("HasAdditionalStorageOption");
- this.maxAdditionalStorage = this.getResponseProperty("MaxAdditionalStorage");
- this.hasPremiumAccessOption = this.getResponseProperty("HasPremiumAccessOption");
this.trialPeriodDays = this.getResponseProperty("TrialPeriodDays");
this.hasSelfHost = this.getResponseProperty("HasSelfHost");
this.hasPolicies = this.getResponseProperty("HasPolicies");
@@ -92,16 +54,46 @@ export class PlanResponse extends BaseResponse {
this.displaySortOrder = this.getResponseProperty("SortOrder");
this.legacyYear = this.getResponseProperty("LegacyYear");
this.disabled = this.getResponseProperty("Disabled");
- this.stripePlanId = this.getResponseProperty("StripePlanId");
+ const passwordManager = this.getResponseProperty("PasswordManager");
+ const secretsManager = this.getResponseProperty("SecretsManager");
+ this.PasswordManager =
+ passwordManager == null ? null : new PasswordManagerPlanFeaturesResponse(passwordManager);
+ this.SecretsManager =
+ secretsManager == null ? null : new SecretsManagerPlanFeaturesResponse(secretsManager);
+ }
+}
+
+export class SecretsManagerPlanFeaturesResponse extends BaseResponse {
+ // Seats
+ stripeSeatPlanId: string;
+ baseSeats: number;
+ basePrice: number;
+ seatPrice: number;
+ hasAdditionalSeatsOption: boolean;
+ maxAdditionalSeats: number;
+ maxSeats: number;
+
+ // Service accounts
+ stripeServiceAccountPlanId: string;
+ additionalPricePerServiceAccount: number;
+ baseServiceAccount: number;
+ maxServiceAccount: number;
+ hasAdditionalServiceAccountOption: boolean;
+ maxAdditionalServiceAccounts: number;
+
+ // Features
+ maxProjects: number;
+
+ constructor(response: any) {
+ super(response);
this.stripeSeatPlanId = this.getResponseProperty("StripeSeatPlanId");
- this.stripeStoragePlanId = this.getResponseProperty("StripeStoragePlanId");
- this.stripePremiumAccessPlanId = this.getResponseProperty("StripePremiumAccessPlanId");
+ this.baseSeats = this.getResponseProperty("BaseSeats");
this.basePrice = this.getResponseProperty("BasePrice");
this.seatPrice = this.getResponseProperty("SeatPrice");
- this.additionalStoragePricePerGb = this.getResponseProperty("AdditionalStoragePricePerGb");
- this.premiumAccessOptionPrice = this.getResponseProperty("PremiumAccessOptionPrice");
-
- this.bitwardenProduct = this.getResponseProperty("BitwardenProduct");
+ this.hasAdditionalSeatsOption = this.getResponseProperty("HasAdditionalSeatsOption");
+ this.maxAdditionalSeats = this.getResponseProperty("MaxAdditionalSeats");
+ this.maxSeats = this.getResponseProperty("MaxSeats");
+ this.stripeServiceAccountPlanId = this.getResponseProperty("StripeServiceAccountPlanId");
this.additionalPricePerServiceAccount = this.getResponseProperty(
"AdditionalPricePerServiceAccount"
);
@@ -110,8 +102,53 @@ export class PlanResponse extends BaseResponse {
this.hasAdditionalServiceAccountOption = this.getResponseProperty(
"HasAdditionalServiceAccountOption"
);
- this.maxProjects = this.getResponseProperty("MaxProjects");
this.maxAdditionalServiceAccounts = this.getResponseProperty("MaxAdditionalServiceAccounts");
- this.stripeServiceAccountPlanId = this.getResponseProperty("StripeServiceAccountPlanId");
+ this.maxProjects = this.getResponseProperty("MaxProjects");
+ }
+}
+
+export class PasswordManagerPlanFeaturesResponse extends BaseResponse {
+ // Seats
+ stripePlanId: string;
+ stripeSeatPlanId: string;
+ stripePremiumAccessPlanId: string;
+ basePrice: number;
+ seatPrice: number;
+ premiumAccessOptionPrice: number;
+ baseSeats: number;
+ maxAdditionalSeats: number;
+ maxSeats: number;
+ hasPremiumAccessOption: boolean;
+
+ // Storage
+ additionalStoragePricePerGb: number;
+ stripeStoragePlanId: string;
+ baseStorageGb: number;
+ hasAdditionalStorageOption: boolean;
+ maxAdditionalStorage: number;
+ hasAdditionalSeatsOption: boolean;
+
+ // Feature
+ maxCollections: number;
+
+ constructor(response: any) {
+ super(response);
+ this.stripePlanId = this.getResponseProperty("StripePlanId");
+ this.stripeSeatPlanId = this.getResponseProperty("StripeSeatPlanId");
+ this.stripeStoragePlanId = this.getResponseProperty("StripeStoragePlanId");
+ this.stripePremiumAccessPlanId = this.getResponseProperty("StripePremiumAccessPlanId");
+ this.basePrice = this.getResponseProperty("BasePrice");
+ this.seatPrice = this.getResponseProperty("SeatPrice");
+ this.baseSeats = this.getResponseProperty("BaseSeats");
+ this.maxAdditionalSeats = this.getResponseProperty("MaxAdditionalSeats");
+ this.premiumAccessOptionPrice = this.getResponseProperty("PremiumAccessOptionPrice");
+ this.maxSeats = this.getResponseProperty("MaxSeats");
+ this.additionalStoragePricePerGb = this.getResponseProperty("AdditionalStoragePricePerGb");
+ this.hasAdditionalSeatsOption = this.getResponseProperty("HasAdditionalSeatsOption");
+ this.baseStorageGb = this.getResponseProperty("BaseStorageGb");
+ this.maxCollections = this.getResponseProperty("MaxCollections");
+ this.hasAdditionalStorageOption = this.getResponseProperty("HasAdditionalStorageOption");
+ this.maxAdditionalStorage = this.getResponseProperty("MaxAdditionalStorage");
+ this.hasPremiumAccessOption = this.getResponseProperty("HasPremiumAccessOption");
}
}
diff --git a/libs/common/src/billing/models/response/subscription.response.ts b/libs/common/src/billing/models/response/subscription.response.ts
index 29850eb767..fd84cf493d 100644
--- a/libs/common/src/billing/models/response/subscription.response.ts
+++ b/libs/common/src/billing/models/response/subscription.response.ts
@@ -1,5 +1,4 @@
import { BaseResponse } from "../../../models/response/base.response";
-import { BitwardenProductType } from "../../enums";
export class SubscriptionResponse extends BaseResponse {
storageName: string;
@@ -67,7 +66,7 @@ export class BillingSubscriptionItemResponse extends BaseResponse {
interval: string;
sponsoredSubscriptionItem: boolean;
addonSubscriptionItem: boolean;
- bitwardenProduct: BitwardenProductType;
+ productName: string;
constructor(response: any) {
super(response);
@@ -77,7 +76,6 @@ export class BillingSubscriptionItemResponse extends BaseResponse {
this.interval = this.getResponseProperty("Interval");
this.sponsoredSubscriptionItem = this.getResponseProperty("SponsoredSubscriptionItem");
this.addonSubscriptionItem = this.getResponseProperty("AddonSubscriptionItem");
- this.bitwardenProduct = this.getResponseProperty("BitwardenProduct");
}
}
diff --git a/libs/common/src/services/api.service.ts b/libs/common/src/services/api.service.ts
index 9f658a0955..d97150096a 100644
--- a/libs/common/src/services/api.service.ts
+++ b/libs/common/src/services/api.service.ts
@@ -894,7 +894,7 @@ export class ApiService implements ApiServiceAbstraction {
// Plan APIs
async getPlans(): Promise> {
- const r = await this.send("GET", "/plans/all", null, false, true);
+ const r = await this.send("GET", "/plans", null, false, true);
return new ListResponse(r, PlanResponse);
}