1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-12 06:13:38 +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) { 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() {

View File

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

View File

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