mirror of
https://github.com/bitwarden/browser
synced 2025-12-12 14:23:32 +00:00
[PM 26691] [Fix: Remove dollar amount from total section when redeeming free families for enterprise (#16887)
* Resolve the dollar amount issue * Resolve the non addition of storage amount * Resolve the estimate tax amount * Fix the improper tax calculation * resolv ethe duplicate code * Added changes to apply the discount only for acceptingSponsorship = true
This commit is contained in:
@@ -670,6 +670,9 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
|
|||||||
if (this.selectedPlan.PasswordManager.hasPremiumAccessOption) {
|
if (this.selectedPlan.PasswordManager.hasPremiumAccessOption) {
|
||||||
subTotal += this.selectedPlan.PasswordManager.premiumAccessOptionPrice;
|
subTotal += this.selectedPlan.PasswordManager.premiumAccessOptionPrice;
|
||||||
}
|
}
|
||||||
|
if (this.selectedPlan.PasswordManager.hasAdditionalStorageOption) {
|
||||||
|
subTotal += this.additionalStorageTotal(this.selectedPlan);
|
||||||
|
}
|
||||||
return subTotal - this.discount;
|
return subTotal - this.discount;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -707,18 +710,9 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.organization.useSecretsManager) {
|
if (this.organization.useSecretsManager) {
|
||||||
return (
|
return this.passwordManagerSubtotal + this.secretsManagerSubtotal() + this.estimatedTax;
|
||||||
this.passwordManagerSubtotal +
|
|
||||||
this.additionalStorageTotal(this.selectedPlan) +
|
|
||||||
this.secretsManagerSubtotal() +
|
|
||||||
this.estimatedTax
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
return (
|
return this.passwordManagerSubtotal + this.estimatedTax;
|
||||||
this.passwordManagerSubtotal +
|
|
||||||
this.additionalStorageTotal(this.selectedPlan) +
|
|
||||||
this.estimatedTax
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get teamsStarterPlanIsAvailable() {
|
get teamsStarterPlanIsAvailable() {
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ import { ToastService } from "@bitwarden/components";
|
|||||||
import { KeyService } from "@bitwarden/key-management";
|
import { KeyService } from "@bitwarden/key-management";
|
||||||
import {
|
import {
|
||||||
OrganizationSubscriptionPlan,
|
OrganizationSubscriptionPlan,
|
||||||
|
OrganizationSubscriptionPurchase,
|
||||||
SubscriberBillingClient,
|
SubscriberBillingClient,
|
||||||
TaxClient,
|
TaxClient,
|
||||||
} from "@bitwarden/web-vault/app/billing/clients";
|
} from "@bitwarden/web-vault/app/billing/clients";
|
||||||
@@ -478,7 +479,10 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get passwordManagerSubtotal() {
|
get passwordManagerSubtotal() {
|
||||||
let subTotal = this.selectedPlan.PasswordManager.basePrice;
|
const basePriceAfterDiscount = this.acceptingSponsorship
|
||||||
|
? Math.max(this.selectedPlan.PasswordManager.basePrice - this.discount, 0)
|
||||||
|
: this.selectedPlan.PasswordManager.basePrice;
|
||||||
|
let subTotal = basePriceAfterDiscount;
|
||||||
if (
|
if (
|
||||||
this.selectedPlan.PasswordManager.hasAdditionalSeatsOption &&
|
this.selectedPlan.PasswordManager.hasAdditionalSeatsOption &&
|
||||||
this.formGroup.controls.additionalSeats.value
|
this.formGroup.controls.additionalSeats.value
|
||||||
@@ -488,19 +492,19 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy {
|
|||||||
this.formGroup.value.additionalSeats,
|
this.formGroup.value.additionalSeats,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (
|
|
||||||
this.selectedPlan.PasswordManager.hasAdditionalStorageOption &&
|
|
||||||
this.formGroup.controls.additionalStorage.value
|
|
||||||
) {
|
|
||||||
subTotal += this.additionalStorageTotal(this.selectedPlan);
|
|
||||||
}
|
|
||||||
if (
|
if (
|
||||||
this.selectedPlan.PasswordManager.hasPremiumAccessOption &&
|
this.selectedPlan.PasswordManager.hasPremiumAccessOption &&
|
||||||
this.formGroup.controls.premiumAccessAddon.value
|
this.formGroup.controls.premiumAccessAddon.value
|
||||||
) {
|
) {
|
||||||
subTotal += this.selectedPlan.PasswordManager.premiumAccessOptionPrice;
|
subTotal += this.selectedPlan.PasswordManager.premiumAccessOptionPrice;
|
||||||
}
|
}
|
||||||
return subTotal - this.discount;
|
if (
|
||||||
|
this.selectedPlan.PasswordManager.hasAdditionalStorageOption &&
|
||||||
|
this.formGroup.controls.additionalStorage.value
|
||||||
|
) {
|
||||||
|
subTotal += this.additionalStorageTotal(this.selectedPlan);
|
||||||
|
}
|
||||||
|
return subTotal;
|
||||||
}
|
}
|
||||||
|
|
||||||
get secretsManagerSubtotal() {
|
get secretsManagerSubtotal() {
|
||||||
@@ -707,54 +711,90 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getPlanFromLegacyEnum(): OrganizationSubscriptionPlan {
|
||||||
|
switch (this.formGroup.value.plan) {
|
||||||
|
case PlanType.FamiliesAnnually:
|
||||||
|
return { tier: "families", cadence: "annually" };
|
||||||
|
case PlanType.TeamsMonthly:
|
||||||
|
return { tier: "teams", cadence: "monthly" };
|
||||||
|
case PlanType.TeamsAnnually:
|
||||||
|
return { tier: "teams", cadence: "annually" };
|
||||||
|
case PlanType.EnterpriseMonthly:
|
||||||
|
return { tier: "enterprise", cadence: "monthly" };
|
||||||
|
case PlanType.EnterpriseAnnually:
|
||||||
|
return { tier: "enterprise", cadence: "annually" };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private buildTaxPreviewRequest(
|
||||||
|
additionalStorage: number,
|
||||||
|
sponsored: boolean,
|
||||||
|
): OrganizationSubscriptionPurchase {
|
||||||
|
const passwordManagerSeats = this.selectedPlan.PasswordManager.hasAdditionalSeatsOption
|
||||||
|
? this.formGroup.value.additionalSeats
|
||||||
|
: 1;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...this.getPlanFromLegacyEnum(),
|
||||||
|
passwordManager: {
|
||||||
|
seats: passwordManagerSeats,
|
||||||
|
additionalStorage,
|
||||||
|
sponsored,
|
||||||
|
},
|
||||||
|
secretsManager: this.formGroup.value.secretsManager.enabled
|
||||||
|
? {
|
||||||
|
seats: this.secretsManagerForm.value.userSeats,
|
||||||
|
additionalServiceAccounts: this.secretsManagerForm.value.additionalServiceAccounts,
|
||||||
|
standalone: false,
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private async refreshSalesTax(): Promise<void> {
|
private async refreshSalesTax(): Promise<void> {
|
||||||
if (this.billingFormGroup.controls.billingAddress.invalid) {
|
if (this.billingFormGroup.controls.billingAddress.invalid) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const getPlanFromLegacyEnum = (): OrganizationSubscriptionPlan => {
|
|
||||||
switch (this.formGroup.value.plan) {
|
|
||||||
case PlanType.FamiliesAnnually:
|
|
||||||
return { tier: "families", cadence: "annually" };
|
|
||||||
case PlanType.TeamsMonthly:
|
|
||||||
return { tier: "teams", cadence: "monthly" };
|
|
||||||
case PlanType.TeamsAnnually:
|
|
||||||
return { tier: "teams", cadence: "annually" };
|
|
||||||
case PlanType.EnterpriseMonthly:
|
|
||||||
return { tier: "enterprise", cadence: "monthly" };
|
|
||||||
case PlanType.EnterpriseAnnually:
|
|
||||||
return { tier: "enterprise", cadence: "annually" };
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const billingAddress = getBillingAddressFromForm(this.billingFormGroup.controls.billingAddress);
|
const billingAddress = getBillingAddressFromForm(this.billingFormGroup.controls.billingAddress);
|
||||||
|
|
||||||
const passwordManagerSeats =
|
// should still be taxed. We mark the plan as NOT sponsored when there is additional storage
|
||||||
this.formGroup.value.productTier === ProductTierType.Families
|
// so the server calculates tax, but we'll adjust the calculation to only tax the storage.
|
||||||
? 1
|
const hasPaidStorage = (this.formGroup.value.additionalStorage || 0) > 0;
|
||||||
: this.formGroup.value.additionalSeats;
|
const sponsoredForTaxPreview = this.acceptingSponsorship && !hasPaidStorage;
|
||||||
|
|
||||||
const taxAmounts = await this.taxClient.previewTaxForOrganizationSubscriptionPurchase(
|
if (this.acceptingSponsorship && hasPaidStorage) {
|
||||||
{
|
// For sponsored plans with paid storage, calculate tax only on storage
|
||||||
...getPlanFromLegacyEnum(),
|
// by comparing tax on base+storage vs tax on base only
|
||||||
passwordManager: {
|
//TODO: Move this logic to PreviewOrganizationTaxCommand - https://bitwarden.atlassian.net/browse/PM-27585
|
||||||
seats: passwordManagerSeats,
|
const [baseTaxAmounts, fullTaxAmounts] = await Promise.all([
|
||||||
additionalStorage: this.formGroup.value.additionalStorage,
|
this.taxClient.previewTaxForOrganizationSubscriptionPurchase(
|
||||||
sponsored: false,
|
this.buildTaxPreviewRequest(0, false),
|
||||||
},
|
billingAddress,
|
||||||
secretsManager: this.formGroup.value.secretsManager.enabled
|
),
|
||||||
? {
|
this.taxClient.previewTaxForOrganizationSubscriptionPurchase(
|
||||||
seats: this.secretsManagerForm.value.userSeats,
|
this.buildTaxPreviewRequest(this.formGroup.value.additionalStorage, false),
|
||||||
additionalServiceAccounts: this.secretsManagerForm.value.additionalServiceAccounts,
|
billingAddress,
|
||||||
standalone: false,
|
),
|
||||||
}
|
]);
|
||||||
: undefined,
|
|
||||||
},
|
|
||||||
billingAddress,
|
|
||||||
);
|
|
||||||
|
|
||||||
this.estimatedTax = taxAmounts.tax;
|
// Tax on storage = Tax on (base + storage) - Tax on (base only)
|
||||||
this.total = taxAmounts.total;
|
this.estimatedTax = fullTaxAmounts.tax - baseTaxAmounts.tax;
|
||||||
|
} else {
|
||||||
|
const taxAmounts = await this.taxClient.previewTaxForOrganizationSubscriptionPurchase(
|
||||||
|
this.buildTaxPreviewRequest(this.formGroup.value.additionalStorage, sponsoredForTaxPreview),
|
||||||
|
billingAddress,
|
||||||
|
);
|
||||||
|
|
||||||
|
this.estimatedTax = taxAmounts.tax;
|
||||||
|
}
|
||||||
|
|
||||||
|
const subtotal =
|
||||||
|
this.passwordManagerSubtotal +
|
||||||
|
(this.planOffersSecretsManager && this.secretsManagerForm.value.enabled
|
||||||
|
? this.secretsManagerSubtotal
|
||||||
|
: 0);
|
||||||
|
this.total = subtotal + this.estimatedTax;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async updateOrganization() {
|
private async updateOrganization() {
|
||||||
|
|||||||
@@ -50,6 +50,9 @@ export class PricingSummaryService {
|
|||||||
if (plan.PasswordManager?.hasPremiumAccessOption) {
|
if (plan.PasswordManager?.hasPremiumAccessOption) {
|
||||||
passwordManagerSubtotal += plan.PasswordManager.premiumAccessOptionPrice;
|
passwordManagerSubtotal += plan.PasswordManager.premiumAccessOptionPrice;
|
||||||
}
|
}
|
||||||
|
if (plan.PasswordManager?.hasAdditionalStorageOption) {
|
||||||
|
passwordManagerSubtotal += additionalStorageTotal;
|
||||||
|
}
|
||||||
|
|
||||||
const secretsManagerSubtotal = plan.SecretsManager
|
const secretsManagerSubtotal = plan.SecretsManager
|
||||||
? (plan.SecretsManager.basePrice || 0) +
|
? (plan.SecretsManager.basePrice || 0) +
|
||||||
@@ -66,8 +69,8 @@ export class PricingSummaryService {
|
|||||||
const storageGb = sub?.maxStorageGb ? sub?.maxStorageGb - 1 : 0;
|
const storageGb = sub?.maxStorageGb ? sub?.maxStorageGb - 1 : 0;
|
||||||
|
|
||||||
const total = organization?.useSecretsManager
|
const total = organization?.useSecretsManager
|
||||||
? passwordManagerSubtotal + additionalStorageTotal + secretsManagerSubtotal + estimatedTax
|
? passwordManagerSubtotal + secretsManagerSubtotal + estimatedTax
|
||||||
: passwordManagerSubtotal + additionalStorageTotal + estimatedTax;
|
: passwordManagerSubtotal + estimatedTax;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
selectedPlanInterval: selectedInterval === PlanInterval.Annually ? "year" : "month",
|
selectedPlanInterval: selectedInterval === PlanInterval.Annually ? "year" : "month",
|
||||||
|
|||||||
Reference in New Issue
Block a user