1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-11 05:43:41 +00:00

Refactor StaticStore Plans and consuming logic (#6136)

* staticstore factoring changes

* Refactoring code changes

* fix the free org issue

* remove a depreciated endpoint

* Resolve the issue of secrets manager sub

* Fix the ui product sorting
This commit is contained in:
cyprain-okeke
2023-10-17 15:56:59 +01:00
committed by GitHub
parent d4e6793871
commit 5cacd79d8c
12 changed files with 313 additions and 195 deletions

View File

@@ -14,20 +14,48 @@
formControlName="plan" formControlName="plan"
/> />
<ng-container *ngIf="selectablePlan.isAnnual"> <ng-container *ngIf="selectablePlan.isAnnual">
{{ "annual" | i18n }} - <ng-container *ngIf="selectablePlan.PasswordManager">
{{ {{ "annual" | i18n }} -
(selectablePlan.basePrice === 0 ? selectablePlan.seatPrice : selectablePlan.basePrice) {{
| currency : "$" (selectablePlan.PasswordManager.basePrice === 0
}} ? selectablePlan.PasswordManager.seatPrice
/{{ "yr" | i18n }} : selectablePlan.PasswordManager.basePrice
) | currency : "$"
}}
/{{ "yr" | i18n }}
</ng-container>
<ng-container *ngIf="selectablePlan.SecretsManager">
{{ "annual" | i18n }} -
{{
(selectablePlan.SecretsManager.basePrice === 0
? selectablePlan.SecretsManager.seatPrice
: selectablePlan.SecretsManager.basePrice
) | currency : "$"
}}
/{{ "yr" | i18n }}
</ng-container>
</ng-container> </ng-container>
<ng-container *ngIf="!selectablePlan.isAnnual"> <ng-container *ngIf="!selectablePlan.isAnnual">
{{ "monthly" | i18n }} - <ng-container *ngIf="selectablePlan.PasswordManager">
{{ {{ "monthly" | i18n }} -
(selectablePlan.basePrice === 0 ? selectablePlan.seatPrice : selectablePlan.basePrice) {{
| currency : "$" (selectablePlan.PasswordManager.basePrice === 0
}} ? selectablePlan.PasswordManager.seatPrice
/{{ "monthAbbr" | i18n }} : selectablePlan.PasswordManager.basePrice
) | currency : "$"
}}
/{{ "monthAbbr" | i18n }}
</ng-container>
<ng-container *ngIf="selectablePlan.SecretsManager">
{{ "monthly" | i18n }} -
{{
(selectablePlan.SecretsManager.basePrice === 0
? selectablePlan.SecretsManager.seatPrice
: selectablePlan.SecretsManager.basePrice
) | currency : "$"
}}
/{{ "monthAbbr" | i18n }}
</ng-container>
</ng-container> </ng-container>
</label> </label>
</div> </div>

View File

@@ -68,23 +68,38 @@
</ng-container> </ng-container>
<ng-template #fullFeatureList> <ng-template #fullFeatureList>
<small *ngIf="selectableProduct.product == productTypes.Free" <small *ngIf="selectableProduct.product == productTypes.Free"
>• {{ "limitedUsers" | i18n : selectableProduct.maxUsers }}</small >• {{ "limitedUsers" | i18n : selectableProduct.PasswordManager.maxSeats }}</small
> >
<small *ngIf="selectableProduct.product != productTypes.Free && selectableProduct.maxUsers" <small
>• {{ "addShareLimitedUsers" | i18n : selectableProduct.maxUsers }}</small *ngIf="
selectableProduct.product != productTypes.Free &&
selectableProduct.PasswordManager.maxSeats
"
>• {{ "addShareLimitedUsers" | i18n : selectableProduct.PasswordManager.maxSeats }}</small
> >
<small *ngIf="!selectableProduct.maxUsers">• {{ "addShareUnlimitedUsers" | i18n }}</small> <small *ngIf="!selectableProduct.PasswordManager.maxSeats"
<small *ngIf="selectableProduct.maxCollections" >• {{ "addShareUnlimitedUsers" | i18n }}</small
>• {{ "limitedCollections" | i18n : selectableProduct.maxCollections }}</small
> >
<small *ngIf="selectableProduct.maxAdditionalSeats" <small *ngIf="selectableProduct.PasswordManager.maxCollections"
> {{ "addShareLimitedUsers" | i18n : selectableProduct.maxAdditionalSeats }}</small >
{{
"limitedCollections" | i18n : selectableProduct.PasswordManager.maxCollections
}}</small
> >
<small *ngIf="!selectableProduct.maxCollections" <small *ngIf="selectableProduct.PasswordManager.maxAdditionalSeats"
>
{{
"addShareLimitedUsers" | i18n : selectableProduct.PasswordManager.maxAdditionalSeats
}}</small
>
<small *ngIf="!selectableProduct.PasswordManager.maxCollections"
>• {{ "createUnlimitedCollections" | i18n }}</small >• {{ "createUnlimitedCollections" | i18n }}</small
> >
<small *ngIf="selectableProduct.baseStorageGb" <small *ngIf="selectableProduct.PasswordManager.baseStorageGb"
> {{ "gbEncryptedFileStorage" | i18n : selectableProduct.baseStorageGb + "GB" }}</small >
{{
"gbEncryptedFileStorage" | i18n : selectableProduct.PasswordManager.baseStorageGb + "GB"
}}</small
> >
<small *ngIf="selectableProduct.hasGroups">• {{ "controlAccessWithGroups" | i18n }}</small> <small *ngIf="selectableProduct.hasGroups">• {{ "controlAccessWithGroups" | i18n }}</small>
<small *ngIf="selectableProduct.hasApi">• {{ "trackAuditLogs" | i18n }}</small> <small *ngIf="selectableProduct.hasApi">• {{ "trackAuditLogs" | i18n }}</small>
@@ -102,25 +117,40 @@
</small> </small>
</ng-template> </ng-template>
<span *ngIf="selectableProduct.product != productTypes.Free"> <span *ngIf="selectableProduct.product != productTypes.Free">
<ng-container *ngIf="selectableProduct.basePrice && !acceptingSponsorship"> <ng-container *ngIf="selectableProduct.PasswordManager.basePrice && !acceptingSponsorship">
{{ selectableProduct.basePrice / 12 | currency : "$" }} /{{ "month" | i18n }}, {{ selectableProduct.PasswordManager.basePrice / 12 | currency : "$" }} /{{
{{ "includesXUsers" | i18n : selectableProduct.baseSeats }} "month" | i18n
<ng-container *ngIf="selectableProduct.hasAdditionalSeatsOption"> }},
{{ "includesXUsers" | i18n : selectableProduct.PasswordManager.baseSeats }}
<ng-container *ngIf="selectableProduct.PasswordManager.hasAdditionalSeatsOption">
{{ ("additionalUsers" | i18n).toLowerCase() }} {{ ("additionalUsers" | i18n).toLowerCase() }}
{{ selectableProduct.seatPrice / 12 | currency : "$" }} /{{ "month" | i18n }} {{ selectableProduct.PasswordManager.seatPrice / 12 | currency : "$" }} /{{
"month" | i18n
}}
</ng-container> </ng-container>
</ng-container> </ng-container>
</span> </span>
<span *ngIf="!selectableProduct.basePrice && selectableProduct.hasAdditionalSeatsOption"> <span
{{ "costPerUser" | i18n : (selectableProduct.seatPrice / 12 | currency : "$") }} /{{ *ngIf="
"month" | i18n !selectableProduct.PasswordManager.basePrice &&
selectableProduct.PasswordManager.hasAdditionalSeatsOption
"
>
{{
"costPerUser" | i18n : (selectableProduct.PasswordManager.seatPrice / 12 | currency : "$")
}} }}
/{{ "month" | i18n }}
</span> </span>
<span *ngIf="selectableProduct.product == productTypes.Free">{{ "freeForever" | i18n }}</span> <span *ngIf="selectableProduct.product == productTypes.Free">{{ "freeForever" | i18n }}</span>
</label> </label>
</div> </div>
<div *ngIf="formGroup.value.product !== productTypes.Free"> <div *ngIf="formGroup.value.product !== productTypes.Free">
<ng-container *ngIf="selectedPlan.hasAdditionalSeatsOption && !selectedPlan.baseSeats"> <ng-container
*ngIf="
selectedPlan.PasswordManager.hasAdditionalSeatsOption &&
!selectedPlan.PasswordManager.baseSeats
"
>
<h2 class="mt-5">{{ "users" | i18n }}</h2> <h2 class="mt-5">{{ "users" | i18n }}</h2>
<div class="row"> <div class="row">
<div class="col-6"> <div class="col-6">
@@ -139,7 +169,13 @@
</div> </div>
</ng-container> </ng-container>
<h2 class="mt-5">{{ "addons" | i18n }}</h2> <h2 class="mt-5">{{ "addons" | i18n }}</h2>
<div class="row" *ngIf="selectedPlan.hasAdditionalSeatsOption && selectedPlan.baseSeats"> <div
class="row"
*ngIf="
selectedPlan.PasswordManager.hasAdditionalSeatsOption &&
selectedPlan.PasswordManager.baseSeats
"
>
<div class="form-group col-6"> <div class="form-group col-6">
<label for="additionalSeats">{{ "additionalUserSeats" | i18n }}</label> <label for="additionalSeats">{{ "additionalUserSeats" | i18n }}</label>
<input <input
@@ -152,7 +188,9 @@
/> />
<small class="text-muted form-text">{{ <small class="text-muted form-text">{{
"userSeatsAdditionalDesc" "userSeatsAdditionalDesc"
| i18n : selectedPlan.baseSeats : (seatPriceMonthly(selectedPlan) | currency : "$") | i18n
: selectedPlan.PasswordManager.baseSeats
: (seatPriceMonthly(selectedPlan) | currency : "$")
}}</small> }}</small>
</div> </div>
</div> </div>
@@ -178,7 +216,7 @@
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="form-group col-6" *ngIf="selectedPlan.hasPremiumAccessOption"> <div class="form-group col-6" *ngIf="selectedPlan.PasswordManager.hasPremiumAccessOption">
<div class="form-check"> <div class="form-check">
<input <input
id="premiumAccess" id="premiumAccess"
@@ -209,62 +247,72 @@
<label class="form-check-label" for="interval{{ selectablePlan.type }}"> <label class="form-check-label" for="interval{{ selectablePlan.type }}">
<ng-container *ngIf="selectablePlan.isAnnual"> <ng-container *ngIf="selectablePlan.isAnnual">
{{ "annually" | i18n }} {{ "annually" | i18n }}
<small *ngIf="selectablePlan.basePrice"> <small *ngIf="selectablePlan.PasswordManager.basePrice">
{{ "basePrice" | i18n }}: {{ selectablePlan.basePrice / 12 | currency : "$" }} &times; {{ "basePrice" | i18n }}:
12 {{ selectablePlan.PasswordManager.basePrice / 12 | currency : "$" }} &times; 12
{{ "monthAbbr" | i18n }} {{ "monthAbbr" | i18n }}
= =
<ng-container *ngIf="acceptingSponsorship; else notAcceptingSponsorship"> <ng-container *ngIf="acceptingSponsorship; else notAcceptingSponsorship">
<span style="text-decoration: line-through">{{ <span style="text-decoration: line-through">{{
selectablePlan.basePrice | currency : "$" selectablePlan.PasswordManager.basePrice | currency : "$"
}}</span> }}</span>
{{ "freeWithSponsorship" | i18n }} {{ "freeWithSponsorship" | i18n }}
</ng-container> </ng-container>
<ng-template #notAcceptingSponsorship> <ng-template #notAcceptingSponsorship>
{{ selectablePlan.basePrice | currency : "$" }} {{ selectablePlan.PasswordManager.basePrice | currency : "$" }}
/{{ "year" | i18n }} /{{ "year" | i18n }}
</ng-template> </ng-template>
</small> </small>
<small *ngIf="selectablePlan.hasAdditionalSeatsOption"> <small *ngIf="selectablePlan.PasswordManager.hasAdditionalSeatsOption">
<span *ngIf="selectablePlan.baseSeats">{{ "additionalUsers" | i18n }}:</span> <span *ngIf="selectablePlan.PasswordManager.baseSeats"
<span *ngIf="!selectablePlan.baseSeats">{{ "users" | i18n }}:</span> >{{ "additionalUsers" | i18n }}:</span
>
<span *ngIf="!selectablePlan.PasswordManager.baseSeats">{{ "users" | i18n }}:</span>
{{ formGroup.controls["additionalSeats"].value || 0 }} &times; {{ formGroup.controls["additionalSeats"].value || 0 }} &times;
{{ selectablePlan.seatPrice / 12 | currency : "$" }} &times; 12 {{ selectablePlan.PasswordManager.seatPrice / 12 | currency : "$" }} &times; 12
{{ "monthAbbr" | i18n }} = {{ "monthAbbr" | i18n }} =
{{ seatTotal(selectablePlan, formGroup.value.additionalSeats) | currency : "$" }} /{{ {{
"year" | i18n passwordManagerSeatTotal(selectablePlan, formGroup.value.additionalSeats)
| currency : "$"
}} }}
/{{ "year" | i18n }}
</small> </small>
<small *ngIf="selectablePlan.hasAdditionalStorageOption"> <small *ngIf="selectablePlan.PasswordManager.hasAdditionalStorageOption">
{{ "additionalStorageGb" | i18n }}: {{ "additionalStorageGb" | i18n }}:
{{ formGroup.controls["additionalStorage"].value || 0 }} &times; {{ formGroup.controls["additionalStorage"].value || 0 }} &times;
{{ selectablePlan.additionalStoragePricePerGb / 12 | currency : "$" }} &times; 12 {{ selectablePlan.PasswordManager.additionalStoragePricePerGb / 12 | currency : "$" }}
{{ "monthAbbr" | i18n }} = &times; 12 {{ "monthAbbr" | i18n }} =
{{ additionalStorageTotal(selectablePlan) | currency : "$" }} /{{ "year" | i18n }} {{ additionalStorageTotal(selectablePlan) | currency : "$" }} /{{ "year" | i18n }}
</small> </small>
</ng-container> </ng-container>
<ng-container *ngIf="!selectablePlan.isAnnual"> <ng-container *ngIf="!selectablePlan.isAnnual">
{{ "monthly" | i18n }} {{ "monthly" | i18n }}
<small *ngIf="selectablePlan.basePrice"> <small *ngIf="selectablePlan.PasswordManager.basePrice">
{{ "basePrice" | i18n }}: {{ selectablePlan.basePrice | currency : "$" }} {{ "basePrice" | i18n }}:
{{ selectablePlan.PasswordManager.basePrice | currency : "$" }}
{{ "monthAbbr" | i18n }} {{ "monthAbbr" | i18n }}
= =
{{ selectablePlan.basePrice | currency : "$" }} {{ selectablePlan.PasswordManager.basePrice | currency : "$" }}
/{{ "month" | i18n }} /{{ "month" | i18n }}
</small> </small>
<small *ngIf="selectablePlan.hasAdditionalSeatsOption"> <small *ngIf="selectablePlan.PasswordManager.hasAdditionalSeatsOption">
<span *ngIf="selectablePlan.baseSeats">{{ "additionalUsers" | i18n }}:</span> <span *ngIf="selectablePlan.PasswordManager.baseSeats"
<span *ngIf="!selectablePlan.baseSeats">{{ "users" | i18n }}:</span> >{{ "additionalUsers" | i18n }}:</span
>
<span *ngIf="!selectablePlan.PasswordManager.baseSeats">{{ "users" | i18n }}:</span>
{{ formGroup.controls["additionalSeats"].value || 0 }} &times; {{ formGroup.controls["additionalSeats"].value || 0 }} &times;
{{ selectablePlan.seatPrice | currency : "$" }} {{ "monthAbbr" | i18n }} = {{ selectablePlan.PasswordManager.seatPrice | currency : "$" }}
{{ seatTotal(selectablePlan, formGroup.value.additionalSeats) | currency : "$" }} /{{ {{ "monthAbbr" | i18n }} =
"month" | i18n {{
passwordManagerSeatTotal(selectablePlan, formGroup.value.additionalSeats)
| currency : "$"
}} }}
/{{ "month" | i18n }}
</small> </small>
<small *ngIf="selectablePlan.hasAdditionalStorageOption"> <small *ngIf="selectablePlan.PasswordManager.hasAdditionalStorageOption">
{{ "additionalStorageGb" | i18n }}: {{ "additionalStorageGb" | i18n }}:
{{ formGroup.controls["additionalStorage"].value || 0 }} &times; {{ formGroup.controls["additionalStorage"].value || 0 }} &times;
{{ selectablePlan.additionalStoragePricePerGb | currency : "$" }} {{ selectablePlan.PasswordManager.additionalStoragePricePerGb | currency : "$" }}
{{ "monthAbbr" | i18n }} = {{ "monthAbbr" | i18n }} =
{{ additionalStorageTotal(selectablePlan) | currency : "$" }} /{{ "month" | i18n }} {{ additionalStorageTotal(selectablePlan) | currency : "$" }} /{{ "month" | i18n }}
</small> </small>

View File

@@ -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 { OrganizationKeysRequest } from "@bitwarden/common/admin-console/models/request/organization-keys.request";
import { OrganizationUpgradeRequest } from "@bitwarden/common/admin-console/models/request/organization-upgrade.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 { 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 { PlanResponse } from "@bitwarden/common/billing/models/response/plan.response";
import { ProductType } from "@bitwarden/common/enums"; import { ProductType } from "@bitwarden/common/enums";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
@@ -136,12 +136,8 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy {
async ngOnInit() { async ngOnInit() {
if (!this.selfHosted) { if (!this.selfHosted) {
const plans = await this.apiService.getPlans(); const plans = await this.apiService.getPlans();
this.passwordManagerPlans = plans.data.filter( this.passwordManagerPlans = plans.data.filter((plan) => !!plan.PasswordManager);
(plan) => plan.bitwardenProduct === BitwardenProductType.PasswordManager this.secretsManagerPlans = plans.data.filter((plan) => !!plan.SecretsManager);
);
this.secretsManagerPlans = plans.data.filter(
(plan) => plan.bitwardenProduct === BitwardenProductType.SecretsManager
);
if (this.product === ProductType.Enterprise || this.product === ProductType.Teams) { if (this.product === ProductType.Enterprise || this.product === ProductType.Teams) {
this.formGroup.controls.businessOwned.setValue(true); this.formGroup.controls.businessOwned.setValue(true);
@@ -221,7 +217,7 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy {
const familyPlan = this.passwordManagerPlans.find( const familyPlan = this.passwordManagerPlans.find(
(plan) => plan.type === PlanType.FamiliesAnnually (plan) => plan.type === PlanType.FamiliesAnnually
); );
this.discount = familyPlan.basePrice; this.discount = familyPlan.PasswordManager.basePrice;
validPlans = [familyPlan]; validPlans = [familyPlan];
} }
@@ -241,67 +237,78 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy {
additionalStoragePriceMonthly(selectedPlan: PlanResponse) { additionalStoragePriceMonthly(selectedPlan: PlanResponse) {
if (!selectedPlan.isAnnual) { if (!selectedPlan.isAnnual) {
return selectedPlan.additionalStoragePricePerGb; return selectedPlan.PasswordManager.additionalStoragePricePerGb;
} }
return selectedPlan.additionalStoragePricePerGb / 12; return selectedPlan.PasswordManager.additionalStoragePricePerGb / 12;
} }
seatPriceMonthly(selectedPlan: PlanResponse) { seatPriceMonthly(selectedPlan: PlanResponse) {
if (!selectedPlan.isAnnual) { if (!selectedPlan.isAnnual) {
return selectedPlan.seatPrice; return selectedPlan.PasswordManager.seatPrice;
} }
return selectedPlan.seatPrice / 12; return selectedPlan.PasswordManager.seatPrice / 12;
} }
additionalStorageTotal(plan: PlanResponse): number { additionalStorageTotal(plan: PlanResponse): number {
if (!plan.hasAdditionalStorageOption) { if (!plan.PasswordManager.hasAdditionalStorageOption) {
return 0; return 0;
} }
return ( return (
plan.additionalStoragePricePerGb * plan.PasswordManager.additionalStoragePricePerGb *
Math.abs(this.formGroup.controls.additionalStorage.value || 0) Math.abs(this.formGroup.controls.additionalStorage.value || 0)
); );
} }
seatTotal(plan: PlanResponse, seats: number): number { passwordManagerSeatTotal(plan: PlanResponse, seats: number): number {
if (!plan.hasAdditionalSeatsOption) { if (!plan.PasswordManager.hasAdditionalSeatsOption) {
return 0; 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 { additionalServiceAccountTotal(plan: PlanResponse): number {
if (!plan.hasAdditionalServiceAccountOption) { if (!plan.SecretsManager.hasAdditionalServiceAccountOption) {
return 0; return 0;
} }
return ( return (
plan.additionalPricePerServiceAccount * plan.SecretsManager.additionalPricePerServiceAccount *
Math.abs(this.secretsManagerForm.value.additionalServiceAccounts || 0) Math.abs(this.secretsManagerForm.value.additionalServiceAccounts || 0)
); );
} }
get passwordManagerSubtotal() { get passwordManagerSubtotal() {
let subTotal = this.selectedPlan.basePrice; let subTotal = this.selectedPlan.PasswordManager.basePrice;
if ( if (
this.selectedPlan.hasAdditionalSeatsOption && this.selectedPlan.PasswordManager.hasAdditionalSeatsOption &&
this.formGroup.controls.additionalSeats.value this.formGroup.controls.additionalSeats.value
) { ) {
subTotal += this.seatTotal(this.selectedPlan, this.formGroup.value.additionalSeats); subTotal += this.passwordManagerSeatTotal(
this.selectedPlan,
this.formGroup.value.additionalSeats
);
} }
if ( if (
this.selectedPlan.hasAdditionalStorageOption && this.selectedPlan.PasswordManager.hasAdditionalStorageOption &&
this.formGroup.controls.additionalStorage.value this.formGroup.controls.additionalStorage.value
) { ) {
subTotal += this.additionalStorageTotal(this.selectedPlan); subTotal += this.additionalStorageTotal(this.selectedPlan);
} }
if ( if (
this.selectedPlan.hasPremiumAccessOption && this.selectedPlan.PasswordManager.hasPremiumAccessOption &&
this.formGroup.controls.premiumAccessAddon.value this.formGroup.controls.premiumAccessAddon.value
) { ) {
subTotal += this.selectedPlan.premiumAccessOptionPrice; subTotal += this.selectedPlan.PasswordManager.premiumAccessOptionPrice;
} }
return subTotal - this.discount; return subTotal - this.discount;
} }
@@ -315,8 +322,8 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy {
} }
return ( return (
plan.basePrice + plan.SecretsManager.basePrice +
this.seatTotal(plan, formValues.userSeats) + this.secretsManagerSeatTotal(plan, formValues.userSeats) +
this.additionalServiceAccountTotal(plan) this.additionalServiceAccountTotal(plan)
); );
} }
@@ -356,18 +363,18 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy {
changedProduct() { changedProduct() {
this.formGroup.controls.plan.setValue(this.selectablePlans[0].type); this.formGroup.controls.plan.setValue(this.selectablePlans[0].type);
if (!this.selectedPlan.hasPremiumAccessOption) { if (!this.selectedPlan.PasswordManager.hasPremiumAccessOption) {
this.formGroup.controls.premiumAccessAddon.setValue(false); this.formGroup.controls.premiumAccessAddon.setValue(false);
} }
if (!this.selectedPlan.hasAdditionalStorageOption) { if (!this.selectedPlan.PasswordManager.hasAdditionalStorageOption) {
this.formGroup.controls.additionalStorage.setValue(0); this.formGroup.controls.additionalStorage.setValue(0);
} }
if (!this.selectedPlan.hasAdditionalSeatsOption) { if (!this.selectedPlan.PasswordManager.hasAdditionalSeatsOption) {
this.formGroup.controls.additionalSeats.setValue(0); this.formGroup.controls.additionalSeats.setValue(0);
} else if ( } else if (
!this.formGroup.controls.additionalSeats.value && !this.formGroup.controls.additionalSeats.value &&
!this.selectedPlan.baseSeats && !this.selectedPlan.PasswordManager.baseSeats &&
this.selectedPlan.hasAdditionalSeatsOption this.selectedPlan.PasswordManager.hasAdditionalSeatsOption
) { ) {
this.formGroup.controls.additionalSeats.setValue(1); this.formGroup.controls.additionalSeats.setValue(1);
} }
@@ -478,7 +485,8 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy {
request.additionalSeats = this.formGroup.controls.additionalSeats.value; request.additionalSeats = this.formGroup.controls.additionalSeats.value;
request.additionalStorageGb = this.formGroup.controls.additionalStorage.value; request.additionalStorageGb = this.formGroup.controls.additionalStorage.value;
request.premiumAccessAddon = 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.planType = this.selectedPlan.type;
request.billingAddressCountry = this.taxComponent.taxInfo.country; request.billingAddressCountry = this.taxComponent.taxInfo.country;
request.billingAddressPostalCode = this.taxComponent.taxInfo.postalCode; request.billingAddressPostalCode = this.taxComponent.taxInfo.postalCode;
@@ -527,7 +535,7 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy {
request.additionalSeats = this.formGroup.controls.additionalSeats.value; request.additionalSeats = this.formGroup.controls.additionalSeats.value;
request.additionalStorageGb = this.formGroup.controls.additionalStorage.value; request.additionalStorageGb = this.formGroup.controls.additionalStorage.value;
request.premiumAccessAddon = request.premiumAccessAddon =
this.selectedPlan.hasPremiumAccessOption && this.selectedPlan.PasswordManager.hasPremiumAccessOption &&
this.formGroup.controls.premiumAccessAddon.value; this.formGroup.controls.premiumAccessAddon.value;
request.planType = this.selectedPlan.type; request.planType = this.selectedPlan.type;
request.billingAddressPostalCode = this.taxComponent.taxInfo.postalCode; request.billingAddressPostalCode = this.taxComponent.taxInfo.postalCode;
@@ -588,7 +596,10 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy {
private billingSubLabelText(): string { private billingSubLabelText(): string {
const selectedPlan = this.selectedPlan; 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 = ""; let text = "";
if (selectedPlan.isAnnual) { if (selectedPlan.isAnnual) {
@@ -611,11 +622,11 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy {
return; return;
} }
if (this.selectedSecretsManagerPlan.hasAdditionalSeatsOption) { if (this.selectedSecretsManagerPlan.SecretsManager.hasAdditionalSeatsOption) {
request.additionalSmSeats = formValues.userSeats; request.additionalSmSeats = formValues.userSeats;
} }
if (this.selectedSecretsManagerPlan.hasAdditionalServiceAccountOption) { if (this.selectedSecretsManagerPlan.SecretsManager.hasAdditionalServiceAccountOption) {
request.additionalServiceAccounts = formValues.additionalServiceAccounts; request.additionalServiceAccounts = formValues.additionalServiceAccounts;
} }
} }

View File

@@ -71,9 +71,7 @@
<ng-container *ngIf="subscription"> <ng-container *ngIf="subscription">
<tr bitRow *ngFor="let i of lineItems"> <tr bitRow *ngFor="let i of lineItems">
<td bitCell [ngClass]="{ 'tw-pl-20': i.addonSubscriptionItem }"> <td bitCell [ngClass]="{ 'tw-pl-20': i.addonSubscriptionItem }">
<span *ngIf="!i.addonSubscriptionItem" <span *ngIf="!i.addonSubscriptionItem">{{ i.productName }} -</span>
>{{ productName(i.bitwardenProduct) }} -</span
>
{{ i.name }} {{ i.quantity > 1 ? "&times;" + i.quantity : "" }} @ {{ i.name }} {{ i.quantity > 1 ? "&times;" + i.quantity : "" }} @
{{ i.amount | currency : "$" }} {{ i.amount | currency : "$" }}
</td> </td>
@@ -150,7 +148,7 @@
<ng-container *ngIf="showSecretsManagerSubscribe"> <ng-container *ngIf="showSecretsManagerSubscribe">
<div class="tw-mt-7"> <div class="tw-mt-7">
<sm-subscribe-standalone <sm-subscribe-standalone
[plan]="sub.secretsManagerPlan" [plan]="sub.plan"
[organization]="userOrg" [organization]="userOrg"
(onSubscribe)="subscriptionAdjusted()" (onSubscribe)="subscriptionAdjusted()"
></sm-subscribe-standalone> ></sm-subscribe-standalone>

View File

@@ -8,7 +8,7 @@ import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-conso
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { OrganizationApiKeyType } from "@bitwarden/common/admin-console/enums"; import { OrganizationApiKeyType } from "@bitwarden/common/admin-console/enums";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; 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 { OrganizationSubscriptionResponse } from "@bitwarden/common/billing/models/response/organization-subscription.response";
import { BillingSubscriptionItemResponse } from "@bitwarden/common/billing/models/response/subscription.response"; import { BillingSubscriptionItemResponse } from "@bitwarden/common/billing/models/response/subscription.response";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
@@ -74,17 +74,6 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy
.subscribe(); .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() { ngOnDestroy() {
this.destroy$.next(); this.destroy$.next();
this.destroy$.complete(); this.destroy$.complete();
@@ -98,7 +87,20 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy
this.userOrg = this.organizationService.get(this.organizationId); this.userOrg = this.organizationService.get(this.organizationId);
if (this.userOrg.canViewSubscription) { if (this.userOrg.canViewSubscription) {
this.sub = await this.organizationApiService.getSubscription(this.organizationId); 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( const apiKeyResponse = await this.organizationApiService.getApiKeyInformation(
@@ -111,6 +113,7 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy
this.showSecretsManagerSubscribe = this.showSecretsManagerSubscribe =
this.userOrg.canEditSubscription && this.userOrg.canEditSubscription &&
!this.userOrg.hasProvider && !this.userOrg.hasProvider &&
this.sub?.plan?.SecretsManager &&
!this.userOrg.useSecretsManager && !this.userOrg.useSecretsManager &&
!this.subscription?.cancelled && !this.subscription?.cancelled &&
!this.subscriptionMarkedForCancel; !this.subscriptionMarkedForCancel;
@@ -119,7 +122,7 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy
this.userOrg.canEditSubscription && this.userOrg.canEditSubscription &&
this.userOrg.useSecretsManager && this.userOrg.useSecretsManager &&
this.subscription != null && this.subscription != null &&
this.sub.secretsManagerPlan?.hasAdditionalSeatsOption && this.sub.plan?.SecretsManager?.hasAdditionalSeatsOption &&
!this.sub.secretsManagerBeta && !this.sub.secretsManagerBeta &&
!this.subscription.cancelled && !this.subscription.cancelled &&
!this.subscriptionMarkedForCancel; !this.subscriptionMarkedForCancel;
@@ -165,11 +168,11 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy
} }
get storageGbPrice() { get storageGbPrice() {
return this.sub.plan.additionalStoragePricePerGb; return this.sub.plan.PasswordManager.additionalStoragePricePerGb;
} }
get seatPrice() { get seatPrice() {
return this.sub.plan.seatPrice; return this.sub.plan.PasswordManager.seatPrice;
} }
get seats() { get seats() {
@@ -180,13 +183,13 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy
return { return {
seatCount: this.sub.smSeats, seatCount: this.sub.smSeats,
maxAutoscaleSeats: this.sub.maxAutoscaleSmSeats, maxAutoscaleSeats: this.sub.maxAutoscaleSmSeats,
seatPrice: this.sub.secretsManagerPlan.seatPrice, seatPrice: this.sub.plan.SecretsManager.seatPrice,
maxAutoscaleServiceAccounts: this.sub.maxAutoscaleSmServiceAccounts, maxAutoscaleServiceAccounts: this.sub.maxAutoscaleSmServiceAccounts,
additionalServiceAccounts: additionalServiceAccounts:
this.sub.smServiceAccounts - this.sub.secretsManagerPlan.baseServiceAccount, this.sub.smServiceAccounts - this.sub.plan.SecretsManager.baseServiceAccount,
interval: this.sub.secretsManagerPlan.isAnnual ? "year" : "month", interval: this.sub.plan.isAnnual ? "year" : "month",
additionalServiceAccountPrice: this.sub.secretsManagerPlan.additionalPricePerServiceAccount, additionalServiceAccountPrice: this.sub.plan.SecretsManager.additionalPricePerServiceAccount,
baseServiceAccountCount: this.sub.secretsManagerPlan.baseServiceAccount, baseServiceAccountCount: this.sub.plan.SecretsManager.baseServiceAccount,
}; };
} }
@@ -195,7 +198,7 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy
} }
get canAdjustSeats() { get canAdjustSeats() {
return this.sub.plan.hasAdditionalSeatsOption; return this.sub.plan.PasswordManager.hasAdditionalSeatsOption;
} }
get isAdmin() { get isAdmin() {
@@ -391,7 +394,7 @@ function sortSubscriptionItems(
a: BillingSubscriptionItemResponse, a: BillingSubscriptionItemResponse,
b: BillingSubscriptionItemResponse b: BillingSubscriptionItemResponse
) { ) {
if (a.bitwardenProduct == b.bitwardenProduct) { if (a.productName == b.productName) {
if (a.addonSubscriptionItem == b.addonSubscriptionItem) { if (a.addonSubscriptionItem == b.addonSubscriptionItem) {
return 0; return 0;
} }
@@ -401,5 +404,5 @@ function sortSubscriptionItems(
} }
return -1; return -1;
} }
return a.bitwardenProduct - b.bitwardenProduct; return a.productName.localeCompare(b.productName);
} }

View File

@@ -33,10 +33,10 @@ export class SecretsManagerSubscribeStandaloneComponent {
submit = async () => { submit = async () => {
const request = new SecretsManagerSubscribeRequest(); const request = new SecretsManagerSubscribeRequest();
request.additionalSmSeats = this.plan.hasAdditionalSeatsOption request.additionalSmSeats = this.plan.SecretsManager.hasAdditionalSeatsOption
? this.formGroup.value.userSeats ? this.formGroup.value.userSeats
: 0; : 0;
request.additionalServiceAccounts = this.plan.hasAdditionalServiceAccountOption request.additionalServiceAccounts = this.plan.SecretsManager.hasAdditionalServiceAccountOption
? this.formGroup.value.additionalServiceAccounts ? this.formGroup.value.additionalServiceAccounts
: 0; : 0;

View File

@@ -45,14 +45,14 @@
</bit-form-control> </bit-form-control>
<ng-container *ngIf="formGroup.value.enabled"> <ng-container *ngIf="formGroup.value.enabled">
<div *ngIf="selectedPlan.hasAdditionalSeatsOption" class="tw-w-1/2"> <div *ngIf="selectedPlan.SecretsManager.hasAdditionalSeatsOption" class="tw-w-1/2">
<bit-form-field> <bit-form-field>
<bit-label>{{ "userSeats" | i18n }}</bit-label> <bit-label>{{ "userSeats" | i18n }}</bit-label>
<input bitInput formControlName="userSeats" type="number" /> <input bitInput formControlName="userSeats" type="number" />
<bit-hint>{{ "userSeatsHowManyDesc" | i18n }}</bit-hint> <bit-hint>{{ "userSeatsHowManyDesc" | i18n }}</bit-hint>
</bit-form-field> </bit-form-field>
</div> </div>
<div *ngIf="selectedPlan.hasAdditionalServiceAccountOption" class="tw-w-1/2"> <div *ngIf="selectedPlan.SecretsManager.hasAdditionalServiceAccountOption" class="tw-w-1/2">
<bit-form-field> <bit-form-field>
<bit-label>{{ "additionalServiceAccounts" | i18n }}</bit-label> <bit-label>{{ "additionalServiceAccounts" | i18n }}</bit-label>
<input bitInput formControlName="additionalServiceAccounts" type="number" /> <input bitInput formControlName="additionalServiceAccounts" type="number" />

View File

@@ -79,26 +79,26 @@ export class SecretsManagerSubscribeComponent implements OnInit, OnDestroy {
} }
get serviceAccountsIncluded() { get serviceAccountsIncluded() {
return this.selectedPlan.baseServiceAccount; return this.selectedPlan.SecretsManager.baseServiceAccount;
} }
get monthlyCostPerServiceAccount() { get monthlyCostPerServiceAccount() {
return this.selectedPlan.isAnnual return this.selectedPlan.isAnnual
? this.selectedPlan.additionalPricePerServiceAccount / 12 ? this.selectedPlan.SecretsManager.additionalPricePerServiceAccount / 12
: this.selectedPlan.additionalPricePerServiceAccount; : this.selectedPlan.SecretsManager.additionalPricePerServiceAccount;
} }
get maxUsers() { get maxUsers() {
return this.selectedPlan.maxUsers; return this.selectedPlan.SecretsManager.maxSeats;
} }
get maxProjects() { get maxProjects() {
return this.selectedPlan.maxProjects; return this.selectedPlan.SecretsManager.maxProjects;
} }
get monthlyCostPerUser() { get monthlyCostPerUser() {
return this.selectedPlan.isAnnual return this.selectedPlan.isAnnual
? this.selectedPlan.seatPrice / 12 ? this.selectedPlan.SecretsManager.seatPrice / 12
: this.selectedPlan.seatPrice; : this.selectedPlan.SecretsManager.seatPrice;
} }
} }

View File

@@ -13,7 +13,6 @@ export class OrganizationResponse extends BaseResponse {
businessTaxNumber: string; businessTaxNumber: string;
billingEmail: string; billingEmail: string;
plan: PlanResponse; plan: PlanResponse;
secretsManagerPlan: PlanResponse;
planType: PlanType; planType: PlanType;
seats: number; seats: number;
maxAutoscaleSeats: number; maxAutoscaleSeats: number;
@@ -49,10 +48,6 @@ export class OrganizationResponse extends BaseResponse {
const plan = this.getResponseProperty("Plan"); const plan = this.getResponseProperty("Plan");
this.plan = plan == null ? null : new PlanResponse(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.planType = this.getResponseProperty("PlanType");
this.seats = this.getResponseProperty("Seats"); this.seats = this.getResponseProperty("Seats");
this.maxAutoscaleSeats = this.getResponseProperty("MaxAutoscaleSeats"); this.maxAutoscaleSeats = this.getResponseProperty("MaxAutoscaleSeats");

View File

@@ -1,28 +1,16 @@
import { ProductType } from "../../../enums"; import { ProductType } from "../../../enums";
import { BaseResponse } from "../../../models/response/base.response"; import { BaseResponse } from "../../../models/response/base.response";
import { BitwardenProductType, PlanType } from "../../enums"; import { PlanType } from "../../enums";
export class PlanResponse extends BaseResponse { export class PlanResponse extends BaseResponse {
type: PlanType; type: PlanType;
product: ProductType; product: ProductType;
bitwardenProduct: BitwardenProductType;
name: string; name: string;
isAnnual: boolean; isAnnual: boolean;
nameLocalizationKey: string; nameLocalizationKey: string;
descriptionLocalizationKey: string; descriptionLocalizationKey: string;
canBeUsedByBusiness: boolean; canBeUsedByBusiness: boolean;
baseSeats: number;
baseStorageGb: number;
maxCollections: number;
maxUsers: number;
hasAdditionalSeatsOption: boolean;
maxAdditionalSeats: number;
hasAdditionalStorageOption: boolean;
maxAdditionalStorage: number;
hasPremiumAccessOption: boolean;
trialPeriodDays: number; trialPeriodDays: number;
hasSelfHost: boolean; hasSelfHost: boolean;
hasPolicies: boolean; hasPolicies: boolean;
hasGroups: boolean; hasGroups: boolean;
@@ -34,29 +22,12 @@ export class PlanResponse extends BaseResponse {
hasSso: boolean; hasSso: boolean;
hasResetPassword: boolean; hasResetPassword: boolean;
usersGetPremium: boolean; usersGetPremium: boolean;
upgradeSortOrder: number; upgradeSortOrder: number;
displaySortOrder: number; displaySortOrder: number;
legacyYear: number; legacyYear: number;
disabled: boolean; disabled: boolean;
PasswordManager: PasswordManagerPlanFeaturesResponse;
stripePlanId: string; SecretsManager: SecretsManagerPlanFeaturesResponse;
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;
constructor(response: any) { constructor(response: any) {
super(response); super(response);
@@ -67,15 +38,6 @@ export class PlanResponse extends BaseResponse {
this.nameLocalizationKey = this.getResponseProperty("NameLocalizationKey"); this.nameLocalizationKey = this.getResponseProperty("NameLocalizationKey");
this.descriptionLocalizationKey = this.getResponseProperty("DescriptionLocalizationKey"); this.descriptionLocalizationKey = this.getResponseProperty("DescriptionLocalizationKey");
this.canBeUsedByBusiness = this.getResponseProperty("CanBeUsedByBusiness"); 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.trialPeriodDays = this.getResponseProperty("TrialPeriodDays");
this.hasSelfHost = this.getResponseProperty("HasSelfHost"); this.hasSelfHost = this.getResponseProperty("HasSelfHost");
this.hasPolicies = this.getResponseProperty("HasPolicies"); this.hasPolicies = this.getResponseProperty("HasPolicies");
@@ -92,16 +54,46 @@ export class PlanResponse extends BaseResponse {
this.displaySortOrder = this.getResponseProperty("SortOrder"); this.displaySortOrder = this.getResponseProperty("SortOrder");
this.legacyYear = this.getResponseProperty("LegacyYear"); this.legacyYear = this.getResponseProperty("LegacyYear");
this.disabled = this.getResponseProperty("Disabled"); 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.stripeSeatPlanId = this.getResponseProperty("StripeSeatPlanId");
this.stripeStoragePlanId = this.getResponseProperty("StripeStoragePlanId"); this.baseSeats = this.getResponseProperty("BaseSeats");
this.stripePremiumAccessPlanId = this.getResponseProperty("StripePremiumAccessPlanId");
this.basePrice = this.getResponseProperty("BasePrice"); this.basePrice = this.getResponseProperty("BasePrice");
this.seatPrice = this.getResponseProperty("SeatPrice"); this.seatPrice = this.getResponseProperty("SeatPrice");
this.additionalStoragePricePerGb = this.getResponseProperty("AdditionalStoragePricePerGb"); this.hasAdditionalSeatsOption = this.getResponseProperty("HasAdditionalSeatsOption");
this.premiumAccessOptionPrice = this.getResponseProperty("PremiumAccessOptionPrice"); this.maxAdditionalSeats = this.getResponseProperty("MaxAdditionalSeats");
this.maxSeats = this.getResponseProperty("MaxSeats");
this.bitwardenProduct = this.getResponseProperty("BitwardenProduct"); this.stripeServiceAccountPlanId = this.getResponseProperty("StripeServiceAccountPlanId");
this.additionalPricePerServiceAccount = this.getResponseProperty( this.additionalPricePerServiceAccount = this.getResponseProperty(
"AdditionalPricePerServiceAccount" "AdditionalPricePerServiceAccount"
); );
@@ -110,8 +102,53 @@ export class PlanResponse extends BaseResponse {
this.hasAdditionalServiceAccountOption = this.getResponseProperty( this.hasAdditionalServiceAccountOption = this.getResponseProperty(
"HasAdditionalServiceAccountOption" "HasAdditionalServiceAccountOption"
); );
this.maxProjects = this.getResponseProperty("MaxProjects");
this.maxAdditionalServiceAccounts = this.getResponseProperty("MaxAdditionalServiceAccounts"); 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");
} }
} }

View File

@@ -1,5 +1,4 @@
import { BaseResponse } from "../../../models/response/base.response"; import { BaseResponse } from "../../../models/response/base.response";
import { BitwardenProductType } from "../../enums";
export class SubscriptionResponse extends BaseResponse { export class SubscriptionResponse extends BaseResponse {
storageName: string; storageName: string;
@@ -67,7 +66,7 @@ export class BillingSubscriptionItemResponse extends BaseResponse {
interval: string; interval: string;
sponsoredSubscriptionItem: boolean; sponsoredSubscriptionItem: boolean;
addonSubscriptionItem: boolean; addonSubscriptionItem: boolean;
bitwardenProduct: BitwardenProductType; productName: string;
constructor(response: any) { constructor(response: any) {
super(response); super(response);
@@ -77,7 +76,6 @@ export class BillingSubscriptionItemResponse extends BaseResponse {
this.interval = this.getResponseProperty("Interval"); this.interval = this.getResponseProperty("Interval");
this.sponsoredSubscriptionItem = this.getResponseProperty("SponsoredSubscriptionItem"); this.sponsoredSubscriptionItem = this.getResponseProperty("SponsoredSubscriptionItem");
this.addonSubscriptionItem = this.getResponseProperty("AddonSubscriptionItem"); this.addonSubscriptionItem = this.getResponseProperty("AddonSubscriptionItem");
this.bitwardenProduct = this.getResponseProperty("BitwardenProduct");
} }
} }

View File

@@ -894,7 +894,7 @@ export class ApiService implements ApiServiceAbstraction {
// Plan APIs // Plan APIs
async getPlans(): Promise<ListResponse<PlanResponse>> { async getPlans(): Promise<ListResponse<PlanResponse>> {
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); return new ListResponse(r, PlanResponse);
} }