1
0
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:
cyprain-okeke
2025-10-30 11:35:34 +01:00
committed by GitHub
parent 55a6e25c0d
commit e41680df41
3 changed files with 97 additions and 60 deletions

View File

@@ -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() {

View File

@@ -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,12 +711,7 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy {
}
}
private async refreshSalesTax(): Promise<void> {
if (this.billingFormGroup.controls.billingAddress.invalid) {
return;
}
const getPlanFromLegacyEnum = (): OrganizationSubscriptionPlan => {
private getPlanFromLegacyEnum(): OrganizationSubscriptionPlan {
switch (this.formGroup.value.plan) {
case PlanType.FamiliesAnnually:
return { tier: "families", cadence: "annually" };
@@ -725,22 +724,22 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy {
case PlanType.EnterpriseAnnually:
return { tier: "enterprise", cadence: "annually" };
}
};
}
const billingAddress = getBillingAddressFromForm(this.billingFormGroup.controls.billingAddress);
private buildTaxPreviewRequest(
additionalStorage: number,
sponsored: boolean,
): OrganizationSubscriptionPurchase {
const passwordManagerSeats = this.selectedPlan.PasswordManager.hasAdditionalSeatsOption
? this.formGroup.value.additionalSeats
: 1;
const passwordManagerSeats =
this.formGroup.value.productTier === ProductTierType.Families
? 1
: this.formGroup.value.additionalSeats;
const taxAmounts = await this.taxClient.previewTaxForOrganizationSubscriptionPurchase(
{
...getPlanFromLegacyEnum(),
return {
...this.getPlanFromLegacyEnum(),
passwordManager: {
seats: passwordManagerSeats,
additionalStorage: this.formGroup.value.additionalStorage,
sponsored: false,
additionalStorage,
sponsored,
},
secretsManager: this.formGroup.value.secretsManager.enabled
? {
@@ -749,12 +748,53 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy {
standalone: false,
}
: undefined,
},
};
}
private async refreshSalesTax(): Promise<void> {
if (this.billingFormGroup.controls.billingAddress.invalid) {
return;
}
const billingAddress = getBillingAddressFromForm(this.billingFormGroup.controls.billingAddress);
// 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;
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,
),
]);
// 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;
this.total = taxAmounts.total;
}
const subtotal =
this.passwordManagerSubtotal +
(this.planOffersSecretsManager && this.secretsManagerForm.value.enabled
? this.secretsManagerSubtotal
: 0);
this.total = subtotal + this.estimatedTax;
}
private async updateOrganization() {

View File

@@ -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",