diff --git a/apps/web/src/app/billing/accounts/trial-initiation/billing.component.html b/apps/web/src/app/billing/accounts/trial-initiation/billing.component.html index fe1f2b425d..ba50a22b56 100644 --- a/apps/web/src/app/billing/accounts/trial-initiation/billing.component.html +++ b/apps/web/src/app/billing/accounts/trial-initiation/billing.component.html @@ -14,20 +14,48 @@ formControlName="plan" /> - {{ "annual" | i18n }} - - {{ - (selectablePlan.basePrice === 0 ? selectablePlan.seatPrice : selectablePlan.basePrice) - | currency : "$" - }} - /{{ "yr" | i18n }} + + {{ "annual" | i18n }} - + {{ + (selectablePlan.PasswordManager.basePrice === 0 + ? selectablePlan.PasswordManager.seatPrice + : selectablePlan.PasswordManager.basePrice + ) | currency : "$" + }} + /{{ "yr" | i18n }} + + + {{ "annual" | i18n }} - + {{ + (selectablePlan.SecretsManager.basePrice === 0 + ? selectablePlan.SecretsManager.seatPrice + : selectablePlan.SecretsManager.basePrice + ) | currency : "$" + }} + /{{ "yr" | i18n }} + - {{ "monthly" | i18n }} - - {{ - (selectablePlan.basePrice === 0 ? selectablePlan.seatPrice : selectablePlan.basePrice) - | currency : "$" - }} - /{{ "monthAbbr" | i18n }} + + {{ "monthly" | i18n }} - + {{ + (selectablePlan.PasswordManager.basePrice === 0 + ? selectablePlan.PasswordManager.seatPrice + : selectablePlan.PasswordManager.basePrice + ) | currency : "$" + }} + /{{ "monthAbbr" | i18n }} + + + {{ "monthly" | i18n }} - + {{ + (selectablePlan.SecretsManager.basePrice === 0 + ? selectablePlan.SecretsManager.seatPrice + : selectablePlan.SecretsManager.basePrice + ) | currency : "$" + }} + /{{ "monthAbbr" | i18n }} + diff --git a/apps/web/src/app/billing/organizations/organization-plans.component.html b/apps/web/src/app/billing/organizations/organization-plans.component.html index 87a8ad6162..6dd97b1eec 100644 --- a/apps/web/src/app/billing/organizations/organization-plans.component.html +++ b/apps/web/src/app/billing/organizations/organization-plans.component.html @@ -68,23 +68,38 @@ • {{ "limitedUsers" | i18n : selectableProduct.maxUsers }}• {{ "limitedUsers" | i18n : selectableProduct.PasswordManager.maxSeats }} - • {{ "addShareLimitedUsers" | i18n : selectableProduct.maxUsers }}• {{ "addShareLimitedUsers" | i18n : selectableProduct.PasswordManager.maxSeats }} - • {{ "addShareUnlimitedUsers" | i18n }} - • {{ "limitedCollections" | i18n : selectableProduct.maxCollections }}• {{ "addShareUnlimitedUsers" | i18n }} - • {{ "addShareLimitedUsers" | i18n : selectableProduct.maxAdditionalSeats }}• + {{ + "limitedCollections" | i18n : selectableProduct.PasswordManager.maxCollections + }} - • + {{ + "addShareLimitedUsers" | i18n : selectableProduct.PasswordManager.maxAdditionalSeats + }} + • {{ "createUnlimitedCollections" | i18n }} - • {{ "gbEncryptedFileStorage" | i18n : selectableProduct.baseStorageGb + "GB" }}• + {{ + "gbEncryptedFileStorage" | i18n : selectableProduct.PasswordManager.baseStorageGb + "GB" + }} • {{ "controlAccessWithGroups" | i18n }} • {{ "trackAuditLogs" | i18n }} @@ -102,25 +117,40 @@ - - {{ selectableProduct.basePrice / 12 | currency : "$" }} /{{ "month" | i18n }}, - {{ "includesXUsers" | i18n : selectableProduct.baseSeats }} - + + {{ selectableProduct.PasswordManager.basePrice / 12 | currency : "$" }} /{{ + "month" | i18n + }}, + {{ "includesXUsers" | i18n : selectableProduct.PasswordManager.baseSeats }} + {{ ("additionalUsers" | i18n).toLowerCase() }} - {{ selectableProduct.seatPrice / 12 | currency : "$" }} /{{ "month" | i18n }} + {{ selectableProduct.PasswordManager.seatPrice / 12 | currency : "$" }} /{{ + "month" | i18n + }} - - {{ "costPerUser" | i18n : (selectableProduct.seatPrice / 12 | currency : "$") }} /{{ - "month" | i18n + + {{ + "costPerUser" | i18n : (selectableProduct.PasswordManager.seatPrice / 12 | currency : "$") }} + /{{ "month" | i18n }} {{ "freeForever" | i18n }}
- +

{{ "users" | i18n }}

@@ -139,7 +169,13 @@

{{ "addons" | i18n }}

-
+
{{ "userSeatsAdditionalDesc" - | i18n : selectedPlan.baseSeats : (seatPriceMonthly(selectedPlan) | currency : "$") + | i18n + : selectedPlan.PasswordManager.baseSeats + : (seatPriceMonthly(selectedPlan) | currency : "$") }}
@@ -178,7 +216,7 @@
-
+
{{ "annually" | i18n }} - - {{ "basePrice" | i18n }}: {{ selectablePlan.basePrice / 12 | currency : "$" }} × - 12 + + {{ "basePrice" | i18n }}: + {{ selectablePlan.PasswordManager.basePrice / 12 | currency : "$" }} × 12 {{ "monthAbbr" | i18n }} = {{ - selectablePlan.basePrice | currency : "$" + selectablePlan.PasswordManager.basePrice | currency : "$" }} {{ "freeWithSponsorship" | i18n }} - {{ selectablePlan.basePrice | currency : "$" }} + {{ selectablePlan.PasswordManager.basePrice | currency : "$" }} /{{ "year" | i18n }} - - {{ "additionalUsers" | i18n }}: - {{ "users" | i18n }}: + + {{ "additionalUsers" | i18n }}: + {{ "users" | i18n }}: {{ formGroup.controls["additionalSeats"].value || 0 }} × - {{ selectablePlan.seatPrice / 12 | currency : "$" }} × 12 + {{ selectablePlan.PasswordManager.seatPrice / 12 | currency : "$" }} × 12 {{ "monthAbbr" | i18n }} = - {{ seatTotal(selectablePlan, formGroup.value.additionalSeats) | currency : "$" }} /{{ - "year" | i18n + {{ + passwordManagerSeatTotal(selectablePlan, formGroup.value.additionalSeats) + | currency : "$" }} + /{{ "year" | i18n }} - + {{ "additionalStorageGb" | i18n }}: {{ formGroup.controls["additionalStorage"].value || 0 }} × - {{ selectablePlan.additionalStoragePricePerGb / 12 | currency : "$" }} × 12 - {{ "monthAbbr" | i18n }} = + {{ selectablePlan.PasswordManager.additionalStoragePricePerGb / 12 | currency : "$" }} + × 12 {{ "monthAbbr" | i18n }} = {{ additionalStorageTotal(selectablePlan) | currency : "$" }} /{{ "year" | i18n }} {{ "monthly" | i18n }} - - {{ "basePrice" | i18n }}: {{ selectablePlan.basePrice | currency : "$" }} + + {{ "basePrice" | i18n }}: + {{ selectablePlan.PasswordManager.basePrice | currency : "$" }} {{ "monthAbbr" | i18n }} = - {{ selectablePlan.basePrice | currency : "$" }} + {{ selectablePlan.PasswordManager.basePrice | currency : "$" }} /{{ "month" | i18n }} - - {{ "additionalUsers" | i18n }}: - {{ "users" | i18n }}: + + {{ "additionalUsers" | i18n }}: + {{ "users" | i18n }}: {{ formGroup.controls["additionalSeats"].value || 0 }} × - {{ selectablePlan.seatPrice | currency : "$" }} {{ "monthAbbr" | i18n }} = - {{ seatTotal(selectablePlan, formGroup.value.additionalSeats) | currency : "$" }} /{{ - "month" | i18n + {{ selectablePlan.PasswordManager.seatPrice | currency : "$" }} + {{ "monthAbbr" | i18n }} = + {{ + passwordManagerSeatTotal(selectablePlan, formGroup.value.additionalSeats) + | currency : "$" }} + /{{ "month" | i18n }} - + {{ "additionalStorageGb" | i18n }}: {{ formGroup.controls["additionalStorage"].value || 0 }} × - {{ selectablePlan.additionalStoragePricePerGb | currency : "$" }} + {{ selectablePlan.PasswordManager.additionalStoragePricePerGb | currency : "$" }} {{ "monthAbbr" | i18n }} = {{ additionalStorageTotal(selectablePlan) | currency : "$" }} /{{ "month" | i18n }} diff --git a/apps/web/src/app/billing/organizations/organization-plans.component.ts b/apps/web/src/app/billing/organizations/organization-plans.component.ts index b85b578146..9af3296169 100644 --- a/apps/web/src/app/billing/organizations/organization-plans.component.ts +++ b/apps/web/src/app/billing/organizations/organization-plans.component.ts @@ -20,7 +20,7 @@ import { OrganizationCreateRequest } from "@bitwarden/common/admin-console/model import { OrganizationKeysRequest } from "@bitwarden/common/admin-console/models/request/organization-keys.request"; import { OrganizationUpgradeRequest } from "@bitwarden/common/admin-console/models/request/organization-upgrade.request"; import { ProviderOrganizationCreateRequest } from "@bitwarden/common/admin-console/models/request/provider/provider-organization-create.request"; -import { BitwardenProductType, PaymentMethodType, PlanType } from "@bitwarden/common/billing/enums"; +import { PaymentMethodType, PlanType } from "@bitwarden/common/billing/enums"; import { PlanResponse } from "@bitwarden/common/billing/models/response/plan.response"; import { ProductType } from "@bitwarden/common/enums"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; @@ -136,12 +136,8 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { async ngOnInit() { if (!this.selfHosted) { const plans = await this.apiService.getPlans(); - this.passwordManagerPlans = plans.data.filter( - (plan) => plan.bitwardenProduct === BitwardenProductType.PasswordManager - ); - this.secretsManagerPlans = plans.data.filter( - (plan) => plan.bitwardenProduct === BitwardenProductType.SecretsManager - ); + this.passwordManagerPlans = plans.data.filter((plan) => !!plan.PasswordManager); + this.secretsManagerPlans = plans.data.filter((plan) => !!plan.SecretsManager); if (this.product === ProductType.Enterprise || this.product === ProductType.Teams) { this.formGroup.controls.businessOwned.setValue(true); @@ -221,7 +217,7 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { const familyPlan = this.passwordManagerPlans.find( (plan) => plan.type === PlanType.FamiliesAnnually ); - this.discount = familyPlan.basePrice; + this.discount = familyPlan.PasswordManager.basePrice; validPlans = [familyPlan]; } @@ -241,67 +237,78 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { additionalStoragePriceMonthly(selectedPlan: PlanResponse) { if (!selectedPlan.isAnnual) { - return selectedPlan.additionalStoragePricePerGb; + return selectedPlan.PasswordManager.additionalStoragePricePerGb; } - return selectedPlan.additionalStoragePricePerGb / 12; + return selectedPlan.PasswordManager.additionalStoragePricePerGb / 12; } seatPriceMonthly(selectedPlan: PlanResponse) { if (!selectedPlan.isAnnual) { - return selectedPlan.seatPrice; + return selectedPlan.PasswordManager.seatPrice; } - return selectedPlan.seatPrice / 12; + return selectedPlan.PasswordManager.seatPrice / 12; } additionalStorageTotal(plan: PlanResponse): number { - if (!plan.hasAdditionalStorageOption) { + if (!plan.PasswordManager.hasAdditionalStorageOption) { return 0; } return ( - plan.additionalStoragePricePerGb * + plan.PasswordManager.additionalStoragePricePerGb * Math.abs(this.formGroup.controls.additionalStorage.value || 0) ); } - seatTotal(plan: PlanResponse, seats: number): number { - if (!plan.hasAdditionalSeatsOption) { + passwordManagerSeatTotal(plan: PlanResponse, seats: number): number { + if (!plan.PasswordManager.hasAdditionalSeatsOption) { return 0; } - return plan.seatPrice * Math.abs(seats || 0); + return plan.PasswordManager.seatPrice * Math.abs(seats || 0); + } + + secretsManagerSeatTotal(plan: PlanResponse, seats: number): number { + if (!plan.SecretsManager.hasAdditionalSeatsOption) { + return 0; + } + + return plan.SecretsManager.seatPrice * Math.abs(seats || 0); } additionalServiceAccountTotal(plan: PlanResponse): number { - if (!plan.hasAdditionalServiceAccountOption) { + if (!plan.SecretsManager.hasAdditionalServiceAccountOption) { return 0; } return ( - plan.additionalPricePerServiceAccount * + plan.SecretsManager.additionalPricePerServiceAccount * Math.abs(this.secretsManagerForm.value.additionalServiceAccounts || 0) ); } get passwordManagerSubtotal() { - let subTotal = this.selectedPlan.basePrice; + let subTotal = this.selectedPlan.PasswordManager.basePrice; if ( - this.selectedPlan.hasAdditionalSeatsOption && + this.selectedPlan.PasswordManager.hasAdditionalSeatsOption && this.formGroup.controls.additionalSeats.value ) { - subTotal += this.seatTotal(this.selectedPlan, this.formGroup.value.additionalSeats); + subTotal += this.passwordManagerSeatTotal( + this.selectedPlan, + this.formGroup.value.additionalSeats + ); } if ( - this.selectedPlan.hasAdditionalStorageOption && + this.selectedPlan.PasswordManager.hasAdditionalStorageOption && this.formGroup.controls.additionalStorage.value ) { subTotal += this.additionalStorageTotal(this.selectedPlan); } if ( - this.selectedPlan.hasPremiumAccessOption && + this.selectedPlan.PasswordManager.hasPremiumAccessOption && this.formGroup.controls.premiumAccessAddon.value ) { - subTotal += this.selectedPlan.premiumAccessOptionPrice; + subTotal += this.selectedPlan.PasswordManager.premiumAccessOptionPrice; } return subTotal - this.discount; } @@ -315,8 +322,8 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { } return ( - plan.basePrice + - this.seatTotal(plan, formValues.userSeats) + + plan.SecretsManager.basePrice + + this.secretsManagerSeatTotal(plan, formValues.userSeats) + this.additionalServiceAccountTotal(plan) ); } @@ -356,18 +363,18 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { changedProduct() { this.formGroup.controls.plan.setValue(this.selectablePlans[0].type); - if (!this.selectedPlan.hasPremiumAccessOption) { + if (!this.selectedPlan.PasswordManager.hasPremiumAccessOption) { this.formGroup.controls.premiumAccessAddon.setValue(false); } - if (!this.selectedPlan.hasAdditionalStorageOption) { + if (!this.selectedPlan.PasswordManager.hasAdditionalStorageOption) { this.formGroup.controls.additionalStorage.setValue(0); } - if (!this.selectedPlan.hasAdditionalSeatsOption) { + if (!this.selectedPlan.PasswordManager.hasAdditionalSeatsOption) { this.formGroup.controls.additionalSeats.setValue(0); } else if ( !this.formGroup.controls.additionalSeats.value && - !this.selectedPlan.baseSeats && - this.selectedPlan.hasAdditionalSeatsOption + !this.selectedPlan.PasswordManager.baseSeats && + this.selectedPlan.PasswordManager.hasAdditionalSeatsOption ) { this.formGroup.controls.additionalSeats.setValue(1); } @@ -478,7 +485,8 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { request.additionalSeats = this.formGroup.controls.additionalSeats.value; request.additionalStorageGb = this.formGroup.controls.additionalStorage.value; request.premiumAccessAddon = - this.selectedPlan.hasPremiumAccessOption && this.formGroup.controls.premiumAccessAddon.value; + this.selectedPlan.PasswordManager.hasPremiumAccessOption && + this.formGroup.controls.premiumAccessAddon.value; request.planType = this.selectedPlan.type; request.billingAddressCountry = this.taxComponent.taxInfo.country; request.billingAddressPostalCode = this.taxComponent.taxInfo.postalCode; @@ -527,7 +535,7 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { request.additionalSeats = this.formGroup.controls.additionalSeats.value; request.additionalStorageGb = this.formGroup.controls.additionalStorage.value; request.premiumAccessAddon = - this.selectedPlan.hasPremiumAccessOption && + this.selectedPlan.PasswordManager.hasPremiumAccessOption && this.formGroup.controls.premiumAccessAddon.value; request.planType = this.selectedPlan.type; request.billingAddressPostalCode = this.taxComponent.taxInfo.postalCode; @@ -588,7 +596,10 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { private billingSubLabelText(): string { const selectedPlan = this.selectedPlan; - const price = selectedPlan.basePrice === 0 ? selectedPlan.seatPrice : selectedPlan.basePrice; + const price = + selectedPlan.PasswordManager.basePrice === 0 + ? selectedPlan.PasswordManager.seatPrice + : selectedPlan.PasswordManager.basePrice; let text = ""; if (selectedPlan.isAnnual) { @@ -611,11 +622,11 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { return; } - if (this.selectedSecretsManagerPlan.hasAdditionalSeatsOption) { + if (this.selectedSecretsManagerPlan.SecretsManager.hasAdditionalSeatsOption) { request.additionalSmSeats = formValues.userSeats; } - if (this.selectedSecretsManagerPlan.hasAdditionalServiceAccountOption) { + if (this.selectedSecretsManagerPlan.SecretsManager.hasAdditionalServiceAccountOption) { request.additionalServiceAccounts = formValues.additionalServiceAccounts; } } diff --git a/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.html b/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.html index 4d55169c42..bfb94a389e 100644 --- a/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.html +++ b/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.html @@ -71,9 +71,7 @@ - {{ productName(i.bitwardenProduct) }} - + {{ i.productName }} - {{ i.name }} {{ i.quantity > 1 ? "×" + i.quantity : "" }} @ {{ i.amount | currency : "$" }} @@ -150,7 +148,7 @@
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); }