From 421edfb0203f05ec1c6e19a06ddff5690f16a401 Mon Sep 17 00:00:00 2001 From: Stephon Brown Date: Tue, 11 Nov 2025 15:51:01 -0500 Subject: [PATCH] [PM-28034] Pre-Launch Payment Defect Solution (#17331) * fix(billing): update to password manager to signal * fix(billing): take first value so the dialog doesn't show again * fix(billing): add families plan to request builder * fix(billing): feedback and type update * fix(billing): fix selectedplan call --- .../cloud-hosted-premium-vnext.component.ts | 2 + .../upgrade-payment.component.html | 22 ++++---- .../upgrade-payment.component.ts | 52 ++++++++++--------- .../services/organization-billing.service.ts | 1 + 4 files changed, 41 insertions(+), 36 deletions(-) diff --git a/apps/web/src/app/billing/individual/premium/cloud-hosted-premium-vnext.component.ts b/apps/web/src/app/billing/individual/premium/cloud-hosted-premium-vnext.component.ts index 9fb34a6ccf..d78451e4f3 100644 --- a/apps/web/src/app/billing/individual/premium/cloud-hosted-premium-vnext.component.ts +++ b/apps/web/src/app/billing/individual/premium/cloud-hosted-premium-vnext.component.ts @@ -11,6 +11,7 @@ import { of, shareReplay, switchMap, + take, } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; @@ -182,6 +183,7 @@ export class CloudHostedPremiumVNextComponent { this.shouldShowUpgradeDialogOnInit$ .pipe( + take(1), switchMap((shouldShowUpgradeDialogOnInit) => { if (shouldShowUpgradeDialogOnInit) { return from(this.openUpgradeDialog("Premium")); diff --git a/apps/web/src/app/billing/individual/upgrade/upgrade-payment/upgrade-payment.component.html b/apps/web/src/app/billing/individual/upgrade/upgrade-payment/upgrade-payment.component.html index 4911c1f198..45a68136a0 100644 --- a/apps/web/src/app/billing/individual/upgrade/upgrade-payment/upgrade-payment.component.html +++ b/apps/web/src/app/billing/individual/upgrade/upgrade-payment/upgrade-payment.component.html @@ -1,6 +1,6 @@
- {{ upgradeToMessage }} + {{ upgradeToMessage() }}
@if (isFamiliesPlan) { @@ -50,17 +50,15 @@
- @if (passwordManager) { - - @if (isFamiliesPlan) { -

- {{ "paymentChargedWithTrial" | i18n }} -

- } + + @if (isFamiliesPlan) { +

+ {{ "paymentChargedWithTrial" | i18n }} +

}
diff --git a/apps/web/src/app/billing/individual/upgrade/upgrade-payment/upgrade-payment.component.ts b/apps/web/src/app/billing/individual/upgrade/upgrade-payment/upgrade-payment.component.ts index 208d046caa..a824e850db 100644 --- a/apps/web/src/app/billing/individual/upgrade/upgrade-payment/upgrade-payment.component.ts +++ b/apps/web/src/app/billing/individual/upgrade/upgrade-payment/upgrade-payment.component.ts @@ -1,6 +1,7 @@ import { AfterViewInit, Component, + computed, DestroyRef, input, OnInit, @@ -34,7 +35,7 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic import { UnionOfValues } from "@bitwarden/common/vault/types/union-of-values"; import { ButtonModule, DialogModule, ToastService } from "@bitwarden/components"; import { LogService } from "@bitwarden/logging"; -import { CartSummaryComponent, LineItem } from "@bitwarden/pricing"; +import { CartSummaryComponent } from "@bitwarden/pricing"; import { SharedModule } from "@bitwarden/web-vault/app/shared"; import { @@ -104,8 +105,6 @@ export class UpgradePaymentComponent implements OnInit, AfterViewInit { protected readonly account = input.required(); protected goBack = output(); protected complete = output(); - protected selectedPlan: PlanDetails | null = null; - protected hasEnoughAccountCredit$!: Observable; readonly paymentComponent = viewChild.required(EnterPaymentMethodComponent); readonly cartSummaryComponent = viewChild.required(CartSummaryComponent); @@ -116,15 +115,26 @@ export class UpgradePaymentComponent implements OnInit, AfterViewInit { billingAddress: EnterBillingAddressComponent.getFormGroup(), }); + protected readonly selectedPlan = signal(null); protected readonly loading = signal(true); - private pricingTiers$!: Observable; - + protected readonly upgradeToMessage = signal(""); // Cart Summary data - protected passwordManager!: LineItem; - protected estimatedTax$!: Observable; + protected readonly passwordManager = computed(() => { + if (!this.selectedPlan()) { + return { name: "", cost: 0, quantity: 0, cadence: "year" as const }; + } - // Display data - protected upgradeToMessage = ""; + return { + name: this.isFamiliesPlan ? "familiesMembership" : "premiumMembership", + cost: this.selectedPlan()!.details.passwordManager.annualPrice, + quantity: 1, + cadence: "year" as const, + }; + }); + + protected hasEnoughAccountCredit$!: Observable; + private pricingTiers$!: Observable; + protected estimatedTax$!: Observable; constructor( private i18nService: I18nService, @@ -162,19 +172,13 @@ export class UpgradePaymentComponent implements OnInit, AfterViewInit { const planDetails = plans.find((plan) => plan.id === this.selectedPlanId()); if (planDetails) { - this.selectedPlan = { + this.selectedPlan.set({ tier: this.selectedPlanId(), details: planDetails, - }; - this.passwordManager = { - name: this.isFamiliesPlan ? "familiesMembership" : "premiumMembership", - cost: this.selectedPlan.details.passwordManager.annualPrice, - quantity: 1, - cadence: "year", - }; + }); - this.upgradeToMessage = this.i18nService.t( - this.isFamiliesPlan ? "startFreeFamiliesTrial" : "upgradeToPremium", + this.upgradeToMessage.set( + this.i18nService.t(this.isFamiliesPlan ? "startFreeFamiliesTrial" : "upgradeToPremium"), ); } else { this.complete.emit({ status: UpgradePaymentStatus.Closed, organizationId: null }); @@ -228,7 +232,7 @@ export class UpgradePaymentComponent implements OnInit, AfterViewInit { return; } - if (!this.selectedPlan) { + if (!this.selectedPlan()) { throw new Error("No plan selected"); } @@ -260,7 +264,7 @@ export class UpgradePaymentComponent implements OnInit, AfterViewInit { } private async processUpgrade(): Promise { - if (!this.selectedPlan) { + if (!this.selectedPlan()) { throw new Error("No plan selected"); } @@ -308,7 +312,7 @@ export class UpgradePaymentComponent implements OnInit, AfterViewInit { const response = await this.upgradePaymentService.upgradeToFamilies( this.account(), - this.selectedPlan!, + this.selectedPlan()!, paymentMethod, paymentFormValues, ); @@ -344,7 +348,7 @@ export class UpgradePaymentComponent implements OnInit, AfterViewInit { // Create an observable for tax calculation private refreshSalesTax$(): Observable { - if (this.formGroup.invalid || !this.selectedPlan) { + if (this.formGroup.invalid || !this.selectedPlan()) { return of(this.INITIAL_TAX_VALUE); } @@ -353,7 +357,7 @@ export class UpgradePaymentComponent implements OnInit, AfterViewInit { return of(this.INITIAL_TAX_VALUE); } return from( - this.upgradePaymentService.calculateEstimatedTax(this.selectedPlan, billingAddress), + this.upgradePaymentService.calculateEstimatedTax(this.selectedPlan()!, billingAddress), ).pipe( catchError((error: unknown) => { this.logService.error("Tax calculation failed:", error); diff --git a/libs/common/src/billing/services/organization-billing.service.ts b/libs/common/src/billing/services/organization-billing.service.ts index 4120047a15..fdc9c6215c 100644 --- a/libs/common/src/billing/services/organization-billing.service.ts +++ b/libs/common/src/billing/services/organization-billing.service.ts @@ -135,6 +135,7 @@ export class OrganizationBillingService implements OrganizationBillingServiceAbs case PlanType.Free: case PlanType.FamiliesAnnually: case PlanType.FamiliesAnnually2019: + case PlanType.FamiliesAnnually2025: case PlanType.TeamsStarter2023: case PlanType.TeamsStarter: return true;