mirror of
https://github.com/bitwarden/browser
synced 2025-12-11 13:53:34 +00:00
[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
This commit is contained in:
@@ -11,6 +11,7 @@ import {
|
|||||||
of,
|
of,
|
||||||
shareReplay,
|
shareReplay,
|
||||||
switchMap,
|
switchMap,
|
||||||
|
take,
|
||||||
} from "rxjs";
|
} from "rxjs";
|
||||||
|
|
||||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||||
@@ -182,6 +183,7 @@ export class CloudHostedPremiumVNextComponent {
|
|||||||
|
|
||||||
this.shouldShowUpgradeDialogOnInit$
|
this.shouldShowUpgradeDialogOnInit$
|
||||||
.pipe(
|
.pipe(
|
||||||
|
take(1),
|
||||||
switchMap((shouldShowUpgradeDialogOnInit) => {
|
switchMap((shouldShowUpgradeDialogOnInit) => {
|
||||||
if (shouldShowUpgradeDialogOnInit) {
|
if (shouldShowUpgradeDialogOnInit) {
|
||||||
return from(this.openUpgradeDialog("Premium"));
|
return from(this.openUpgradeDialog("Premium"));
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<form [formGroup]="formGroup" [bitSubmit]="submit">
|
<form [formGroup]="formGroup" [bitSubmit]="submit">
|
||||||
<bit-dialog dialogSize="large" [loading]="loading()">
|
<bit-dialog dialogSize="large" [loading]="loading()">
|
||||||
<span bitDialogTitle class="tw-font-medium">{{ upgradeToMessage }}</span>
|
<span bitDialogTitle class="tw-font-medium">{{ upgradeToMessage() }}</span>
|
||||||
<ng-container bitDialogContent>
|
<ng-container bitDialogContent>
|
||||||
<section>
|
<section>
|
||||||
@if (isFamiliesPlan) {
|
@if (isFamiliesPlan) {
|
||||||
@@ -50,10 +50,9 @@
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
@if (passwordManager) {
|
|
||||||
<billing-cart-summary
|
<billing-cart-summary
|
||||||
#cartSummaryComponent
|
#cartSummaryComponent
|
||||||
[passwordManager]="passwordManager"
|
[passwordManager]="passwordManager()"
|
||||||
[estimatedTax]="estimatedTax$ | async"
|
[estimatedTax]="estimatedTax$ | async"
|
||||||
></billing-cart-summary>
|
></billing-cart-summary>
|
||||||
@if (isFamiliesPlan) {
|
@if (isFamiliesPlan) {
|
||||||
@@ -61,7 +60,6 @@
|
|||||||
{{ "paymentChargedWithTrial" | i18n }}
|
{{ "paymentChargedWithTrial" | i18n }}
|
||||||
</p>
|
</p>
|
||||||
}
|
}
|
||||||
}
|
|
||||||
</section>
|
</section>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
AfterViewInit,
|
AfterViewInit,
|
||||||
Component,
|
Component,
|
||||||
|
computed,
|
||||||
DestroyRef,
|
DestroyRef,
|
||||||
input,
|
input,
|
||||||
OnInit,
|
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 { UnionOfValues } from "@bitwarden/common/vault/types/union-of-values";
|
||||||
import { ButtonModule, DialogModule, ToastService } from "@bitwarden/components";
|
import { ButtonModule, DialogModule, ToastService } from "@bitwarden/components";
|
||||||
import { LogService } from "@bitwarden/logging";
|
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 { SharedModule } from "@bitwarden/web-vault/app/shared";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -104,8 +105,6 @@ export class UpgradePaymentComponent implements OnInit, AfterViewInit {
|
|||||||
protected readonly account = input.required<Account>();
|
protected readonly account = input.required<Account>();
|
||||||
protected goBack = output<void>();
|
protected goBack = output<void>();
|
||||||
protected complete = output<UpgradePaymentResult>();
|
protected complete = output<UpgradePaymentResult>();
|
||||||
protected selectedPlan: PlanDetails | null = null;
|
|
||||||
protected hasEnoughAccountCredit$!: Observable<boolean>;
|
|
||||||
|
|
||||||
readonly paymentComponent = viewChild.required(EnterPaymentMethodComponent);
|
readonly paymentComponent = viewChild.required(EnterPaymentMethodComponent);
|
||||||
readonly cartSummaryComponent = viewChild.required(CartSummaryComponent);
|
readonly cartSummaryComponent = viewChild.required(CartSummaryComponent);
|
||||||
@@ -116,15 +115,26 @@ export class UpgradePaymentComponent implements OnInit, AfterViewInit {
|
|||||||
billingAddress: EnterBillingAddressComponent.getFormGroup(),
|
billingAddress: EnterBillingAddressComponent.getFormGroup(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
protected readonly selectedPlan = signal<PlanDetails | null>(null);
|
||||||
protected readonly loading = signal(true);
|
protected readonly loading = signal(true);
|
||||||
private pricingTiers$!: Observable<PersonalSubscriptionPricingTier[]>;
|
protected readonly upgradeToMessage = signal("");
|
||||||
|
|
||||||
// Cart Summary data
|
// Cart Summary data
|
||||||
protected passwordManager!: LineItem;
|
protected readonly passwordManager = computed(() => {
|
||||||
protected estimatedTax$!: Observable<number>;
|
if (!this.selectedPlan()) {
|
||||||
|
return { name: "", cost: 0, quantity: 0, cadence: "year" as const };
|
||||||
|
}
|
||||||
|
|
||||||
// Display data
|
return {
|
||||||
protected upgradeToMessage = "";
|
name: this.isFamiliesPlan ? "familiesMembership" : "premiumMembership",
|
||||||
|
cost: this.selectedPlan()!.details.passwordManager.annualPrice,
|
||||||
|
quantity: 1,
|
||||||
|
cadence: "year" as const,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
protected hasEnoughAccountCredit$!: Observable<boolean>;
|
||||||
|
private pricingTiers$!: Observable<PersonalSubscriptionPricingTier[]>;
|
||||||
|
protected estimatedTax$!: Observable<number>;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private i18nService: I18nService,
|
private i18nService: I18nService,
|
||||||
@@ -162,19 +172,13 @@ export class UpgradePaymentComponent implements OnInit, AfterViewInit {
|
|||||||
const planDetails = plans.find((plan) => plan.id === this.selectedPlanId());
|
const planDetails = plans.find((plan) => plan.id === this.selectedPlanId());
|
||||||
|
|
||||||
if (planDetails) {
|
if (planDetails) {
|
||||||
this.selectedPlan = {
|
this.selectedPlan.set({
|
||||||
tier: this.selectedPlanId(),
|
tier: this.selectedPlanId(),
|
||||||
details: planDetails,
|
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.upgradeToMessage.set(
|
||||||
this.isFamiliesPlan ? "startFreeFamiliesTrial" : "upgradeToPremium",
|
this.i18nService.t(this.isFamiliesPlan ? "startFreeFamiliesTrial" : "upgradeToPremium"),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
this.complete.emit({ status: UpgradePaymentStatus.Closed, organizationId: null });
|
this.complete.emit({ status: UpgradePaymentStatus.Closed, organizationId: null });
|
||||||
@@ -228,7 +232,7 @@ export class UpgradePaymentComponent implements OnInit, AfterViewInit {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.selectedPlan) {
|
if (!this.selectedPlan()) {
|
||||||
throw new Error("No plan selected");
|
throw new Error("No plan selected");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -260,7 +264,7 @@ export class UpgradePaymentComponent implements OnInit, AfterViewInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async processUpgrade(): Promise<UpgradePaymentResult> {
|
private async processUpgrade(): Promise<UpgradePaymentResult> {
|
||||||
if (!this.selectedPlan) {
|
if (!this.selectedPlan()) {
|
||||||
throw new Error("No plan selected");
|
throw new Error("No plan selected");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -308,7 +312,7 @@ export class UpgradePaymentComponent implements OnInit, AfterViewInit {
|
|||||||
|
|
||||||
const response = await this.upgradePaymentService.upgradeToFamilies(
|
const response = await this.upgradePaymentService.upgradeToFamilies(
|
||||||
this.account(),
|
this.account(),
|
||||||
this.selectedPlan!,
|
this.selectedPlan()!,
|
||||||
paymentMethod,
|
paymentMethod,
|
||||||
paymentFormValues,
|
paymentFormValues,
|
||||||
);
|
);
|
||||||
@@ -344,7 +348,7 @@ export class UpgradePaymentComponent implements OnInit, AfterViewInit {
|
|||||||
|
|
||||||
// Create an observable for tax calculation
|
// Create an observable for tax calculation
|
||||||
private refreshSalesTax$(): Observable<number> {
|
private refreshSalesTax$(): Observable<number> {
|
||||||
if (this.formGroup.invalid || !this.selectedPlan) {
|
if (this.formGroup.invalid || !this.selectedPlan()) {
|
||||||
return of(this.INITIAL_TAX_VALUE);
|
return of(this.INITIAL_TAX_VALUE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -353,7 +357,7 @@ export class UpgradePaymentComponent implements OnInit, AfterViewInit {
|
|||||||
return of(this.INITIAL_TAX_VALUE);
|
return of(this.INITIAL_TAX_VALUE);
|
||||||
}
|
}
|
||||||
return from(
|
return from(
|
||||||
this.upgradePaymentService.calculateEstimatedTax(this.selectedPlan, billingAddress),
|
this.upgradePaymentService.calculateEstimatedTax(this.selectedPlan()!, billingAddress),
|
||||||
).pipe(
|
).pipe(
|
||||||
catchError((error: unknown) => {
|
catchError((error: unknown) => {
|
||||||
this.logService.error("Tax calculation failed:", error);
|
this.logService.error("Tax calculation failed:", error);
|
||||||
|
|||||||
@@ -135,6 +135,7 @@ export class OrganizationBillingService implements OrganizationBillingServiceAbs
|
|||||||
case PlanType.Free:
|
case PlanType.Free:
|
||||||
case PlanType.FamiliesAnnually:
|
case PlanType.FamiliesAnnually:
|
||||||
case PlanType.FamiliesAnnually2019:
|
case PlanType.FamiliesAnnually2019:
|
||||||
|
case PlanType.FamiliesAnnually2025:
|
||||||
case PlanType.TeamsStarter2023:
|
case PlanType.TeamsStarter2023:
|
||||||
case PlanType.TeamsStarter:
|
case PlanType.TeamsStarter:
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
Reference in New Issue
Block a user