mirror of
https://github.com/bitwarden/browser
synced 2025-12-10 05:13:29 +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) {
|
||||
subTotal += this.selectedPlan.PasswordManager.premiumAccessOptionPrice;
|
||||
}
|
||||
if (this.selectedPlan.PasswordManager.hasAdditionalStorageOption) {
|
||||
subTotal += this.additionalStorageTotal(this.selectedPlan);
|
||||
}
|
||||
return subTotal - this.discount;
|
||||
}
|
||||
|
||||
@@ -707,18 +710,9 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
if (this.organization.useSecretsManager) {
|
||||
return (
|
||||
this.passwordManagerSubtotal +
|
||||
this.additionalStorageTotal(this.selectedPlan) +
|
||||
this.secretsManagerSubtotal() +
|
||||
this.estimatedTax
|
||||
);
|
||||
return this.passwordManagerSubtotal + this.secretsManagerSubtotal() + this.estimatedTax;
|
||||
}
|
||||
return (
|
||||
this.passwordManagerSubtotal +
|
||||
this.additionalStorageTotal(this.selectedPlan) +
|
||||
this.estimatedTax
|
||||
);
|
||||
return this.passwordManagerSubtotal + this.estimatedTax;
|
||||
}
|
||||
|
||||
get teamsStarterPlanIsAvailable() {
|
||||
|
||||
@@ -49,6 +49,7 @@ import { ToastService } from "@bitwarden/components";
|
||||
import { KeyService } from "@bitwarden/key-management";
|
||||
import {
|
||||
OrganizationSubscriptionPlan,
|
||||
OrganizationSubscriptionPurchase,
|
||||
SubscriberBillingClient,
|
||||
TaxClient,
|
||||
} from "@bitwarden/web-vault/app/billing/clients";
|
||||
@@ -478,7 +479,10 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
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 (
|
||||
this.selectedPlan.PasswordManager.hasAdditionalSeatsOption &&
|
||||
this.formGroup.controls.additionalSeats.value
|
||||
@@ -488,19 +492,19 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy {
|
||||
this.formGroup.value.additionalSeats,
|
||||
);
|
||||
}
|
||||
if (
|
||||
this.selectedPlan.PasswordManager.hasAdditionalStorageOption &&
|
||||
this.formGroup.controls.additionalStorage.value
|
||||
) {
|
||||
subTotal += this.additionalStorageTotal(this.selectedPlan);
|
||||
}
|
||||
if (
|
||||
this.selectedPlan.PasswordManager.hasPremiumAccessOption &&
|
||||
this.formGroup.controls.premiumAccessAddon.value
|
||||
) {
|
||||
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() {
|
||||
@@ -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> {
|
||||
if (this.billingFormGroup.controls.billingAddress.invalid) {
|
||||
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 passwordManagerSeats =
|
||||
this.formGroup.value.productTier === ProductTierType.Families
|
||||
? 1
|
||||
: this.formGroup.value.additionalSeats;
|
||||
// should still be taxed. We mark the plan as NOT sponsored when there is additional storage
|
||||
// so the server calculates tax, but we'll adjust the calculation to only tax the storage.
|
||||
const hasPaidStorage = (this.formGroup.value.additionalStorage || 0) > 0;
|
||||
const sponsoredForTaxPreview = this.acceptingSponsorship && !hasPaidStorage;
|
||||
|
||||
const taxAmounts = await this.taxClient.previewTaxForOrganizationSubscriptionPurchase(
|
||||
{
|
||||
...getPlanFromLegacyEnum(),
|
||||
passwordManager: {
|
||||
seats: passwordManagerSeats,
|
||||
additionalStorage: this.formGroup.value.additionalStorage,
|
||||
sponsored: false,
|
||||
},
|
||||
secretsManager: this.formGroup.value.secretsManager.enabled
|
||||
? {
|
||||
seats: this.secretsManagerForm.value.userSeats,
|
||||
additionalServiceAccounts: this.secretsManagerForm.value.additionalServiceAccounts,
|
||||
standalone: false,
|
||||
}
|
||||
: undefined,
|
||||
},
|
||||
billingAddress,
|
||||
);
|
||||
if (this.acceptingSponsorship && hasPaidStorage) {
|
||||
// For sponsored plans with paid storage, calculate tax only on storage
|
||||
// by comparing tax on base+storage vs tax on base only
|
||||
//TODO: Move this logic to PreviewOrganizationTaxCommand - https://bitwarden.atlassian.net/browse/PM-27585
|
||||
const [baseTaxAmounts, fullTaxAmounts] = await Promise.all([
|
||||
this.taxClient.previewTaxForOrganizationSubscriptionPurchase(
|
||||
this.buildTaxPreviewRequest(0, false),
|
||||
billingAddress,
|
||||
),
|
||||
this.taxClient.previewTaxForOrganizationSubscriptionPurchase(
|
||||
this.buildTaxPreviewRequest(this.formGroup.value.additionalStorage, false),
|
||||
billingAddress,
|
||||
),
|
||||
]);
|
||||
|
||||
this.estimatedTax = taxAmounts.tax;
|
||||
this.total = taxAmounts.total;
|
||||
// Tax on storage = Tax on (base + storage) - Tax on (base only)
|
||||
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() {
|
||||
|
||||
@@ -50,6 +50,9 @@ export class PricingSummaryService {
|
||||
if (plan.PasswordManager?.hasPremiumAccessOption) {
|
||||
passwordManagerSubtotal += plan.PasswordManager.premiumAccessOptionPrice;
|
||||
}
|
||||
if (plan.PasswordManager?.hasAdditionalStorageOption) {
|
||||
passwordManagerSubtotal += additionalStorageTotal;
|
||||
}
|
||||
|
||||
const secretsManagerSubtotal = plan.SecretsManager
|
||||
? (plan.SecretsManager.basePrice || 0) +
|
||||
@@ -66,8 +69,8 @@ export class PricingSummaryService {
|
||||
const storageGb = sub?.maxStorageGb ? sub?.maxStorageGb - 1 : 0;
|
||||
|
||||
const total = organization?.useSecretsManager
|
||||
? passwordManagerSubtotal + additionalStorageTotal + secretsManagerSubtotal + estimatedTax
|
||||
: passwordManagerSubtotal + additionalStorageTotal + estimatedTax;
|
||||
? passwordManagerSubtotal + secretsManagerSubtotal + estimatedTax
|
||||
: passwordManagerSubtotal + estimatedTax;
|
||||
|
||||
return {
|
||||
selectedPlanInterval: selectedInterval === PlanInterval.Annually ? "year" : "month",
|
||||
|
||||
Reference in New Issue
Block a user