[PM-31901] Remove m3 flagged logic (#19868)

* Remove pm-26462-milestone-3 flag from subscription-pricing.service.ts

* Remove pm-26462-milestone-3 flag from organization-plans.component.ts

* Remove pm-26462-milestone-3 flag from change-plan-dialog.component.ts

* Remove pm-26462-milestone-3 flag from create-organization.component.ts

* Remove pm-26462-milestone-3 flag from upgrade-payment.service.ts

* Remove pm-26462-milestone-3 flag from families-for-enterprise-setup.component.ts

* Remove pm-26462-milestone-3 flag from complete-trial-initiation.component.ts

* Remove pm-26462-milestone-3 flag from trial-billing-step.service.ts

* Update tests after pm-26462-milestone-3 removal

* Fix test expectations after pm-26462-milestone-3 removal

Update mock plan types from FamiliesAnnually2025 to FamiliesAnnually
in subscription-pricing and upgrade-payment specs. Delete flag-specific
test cases from upgrade-payment spec.
This commit is contained in:
Conner Turnbull 2026-04-14 15:41:14 -04:00 committed by GitHub
parent 3212ffd4fa
commit 424fa9c382
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 23 additions and 203 deletions

View File

@ -15,8 +15,6 @@ import { PreValidateSponsorshipResponse } from "@bitwarden/common/admin-console/
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { PlanSponsorshipType, PlanType, ProductTierType } from "@bitwarden/common/billing/enums";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service";
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
@ -57,7 +55,6 @@ export class FamiliesForEnterpriseSetupComponent implements OnInit, OnDestroy {
constructor(
private router: Router,
private configService: ConfigService,
private i18nService: I18nService,
private route: ActivatedRoute,
private apiService: ApiService,
@ -108,12 +105,7 @@ export class FamiliesForEnterpriseSetupComponent implements OnInit, OnDestroy {
this.badToken = !this.preValidateSponsorshipResponse.isTokenValid;
}
const milestone3FeatureEnabled = await this.configService.getFeatureFlag(
FeatureFlag.PM26462_Milestone_3,
);
this.familyPlan = milestone3FeatureEnabled
? PlanType.FamiliesAnnually
: PlanType.FamiliesAnnually2025;
this.familyPlan = PlanType.FamiliesAnnually;
this.loading = false;
});

View File

@ -6,8 +6,6 @@ import { Subject, takeUntil } from "rxjs";
import { first } from "rxjs/operators";
import { PlanType, ProductTierType, ProductType } from "@bitwarden/common/billing/enums";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { OrganizationPlansComponent } from "../../billing";
import { HeaderModule } from "../../layouts/header/header.module";
@ -24,24 +22,14 @@ export class CreateOrganizationComponent implements OnInit, OnDestroy {
protected plan: PlanType = PlanType.Free;
protected productTier: ProductTierType = ProductTierType.Free;
constructor(
private route: ActivatedRoute,
private configService: ConfigService,
) {}
constructor(private route: ActivatedRoute) {}
private destroy$ = new Subject<void>();
async ngOnInit(): Promise<void> {
const milestone3FeatureEnabled = await this.configService.getFeatureFlag(
FeatureFlag.PM26462_Milestone_3,
);
const familyPlan = milestone3FeatureEnabled
? PlanType.FamiliesAnnually
: PlanType.FamiliesAnnually2025;
ngOnInit(): void {
this.route.queryParams.pipe(first(), takeUntil(this.destroy$)).subscribe((qParams) => {
if (qParams.plan === "families" || qParams.productTier == ProductTierType.Families) {
this.plan = familyPlan;
this.plan = PlanType.FamiliesAnnually;
this.productTier = ProductTierType.Families;
} else if (qParams.plan === "teams" || qParams.productTier == ProductTierType.Teams) {
this.plan = PlanType.TeamsAnnually;

View File

@ -687,7 +687,7 @@ describe("UpgradePaymentService", () => {
billingEmail: "test@example.com",
},
plan: {
type: PlanType.FamiliesAnnually2025,
type: PlanType.FamiliesAnnually,
passwordManagerSeats: 6,
},
payment: {
@ -703,70 +703,6 @@ describe("UpgradePaymentService", () => {
expect(mockSyncService.fullSync).toHaveBeenCalledWith(true);
});
it("should use FamiliesAnnually2025 plan when feature flag is disabled", async () => {
// Arrange
mockConfigService.getFeatureFlag.mockResolvedValue(false);
mockOrganizationBillingService.purchaseSubscription.mockResolvedValue({
id: "org-id",
name: "Test Organization",
billingEmail: "test@example.com",
} as OrganizationResponse);
// Act
await sut.upgradeToFamilies(
mockAccount,
mockFamiliesPlanDetails,
mockTokenizedPaymentMethod,
{
organizationName: "Test Organization",
billingAddress: mockBillingAddress,
},
);
// Assert
expect(mockOrganizationBillingService.purchaseSubscription).toHaveBeenCalledWith(
expect.objectContaining({
plan: {
type: PlanType.FamiliesAnnually2025,
passwordManagerSeats: 6,
},
}),
"user-id",
);
});
it("should use FamiliesAnnually plan when feature flag is enabled", async () => {
// Arrange
mockConfigService.getFeatureFlag.mockResolvedValue(true);
mockOrganizationBillingService.purchaseSubscription.mockResolvedValue({
id: "org-id",
name: "Test Organization",
billingEmail: "test@example.com",
} as OrganizationResponse);
// Act
await sut.upgradeToFamilies(
mockAccount,
mockFamiliesPlanDetails,
mockTokenizedPaymentMethod,
{
organizationName: "Test Organization",
billingAddress: mockBillingAddress,
},
);
// Assert
expect(mockOrganizationBillingService.purchaseSubscription).toHaveBeenCalledWith(
expect.objectContaining({
plan: {
type: PlanType.FamiliesAnnually,
passwordManagerSeats: 6,
},
}),
"user-id",
);
});
it("should throw error if password manager seats are 0", async () => {
// Arrange
const invalidPlanDetails: PlanDetails = {

View File

@ -16,8 +16,6 @@ import {
PersonalSubscriptionPricingTierId,
PersonalSubscriptionPricingTierIds,
} from "@bitwarden/common/billing/types/subscription-pricing-tier";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
import { LogService } from "@bitwarden/logging";
@ -64,7 +62,6 @@ export class UpgradePaymentService {
private organizationService: OrganizationService,
private accountService: AccountService,
private subscriberBillingClient: SubscriberBillingClient,
private configService: ConfigService,
) {}
userIsOwnerOfFreeOrg$: Observable<boolean> = this.accountService.activeAccount$.pipe(
@ -175,12 +172,7 @@ export class UpgradePaymentService {
this.validatePaymentAndBillingInfo(paymentMethod, billingAddress);
const passwordManagerSeats = this.getPasswordManagerSeats(planDetails);
const milestone3FeatureEnabled = await this.configService.getFeatureFlag(
FeatureFlag.PM26462_Milestone_3,
);
const familyPlan = milestone3FeatureEnabled
? PlanType.FamiliesAnnually
: PlanType.FamiliesAnnually2025;
const familyPlan = PlanType.FamiliesAnnually;
const subscriptionInformation: SubscriptionInformation = {
organization: {

View File

@ -31,9 +31,7 @@ import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { PlanInterval, PlanType, ProductTierType } from "@bitwarden/common/billing/enums";
import { OrganizationSubscriptionResponse } from "@bitwarden/common/billing/models/response/organization-subscription.response";
import { PlanResponse } from "@bitwarden/common/billing/models/response/plan.response";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { ListResponse } from "@bitwarden/common/models/response/list.response";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
import { OrganizationId } from "@bitwarden/common/types/guid";
@ -249,7 +247,6 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
private subscriberBillingClient: SubscriberBillingClient,
private previewInvoiceClient: PreviewInvoiceClient,
private organizationWarningsService: OrganizationWarningsService,
private configService: ConfigService,
) {}
async ngOnInit(): Promise<void> {
@ -299,12 +296,7 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
}
}
const milestone3FeatureEnabled = await this.configService.getFeatureFlag(
FeatureFlag.PM26462_Milestone_3,
);
this._familyPlan = milestone3FeatureEnabled
? PlanType.FamiliesAnnually
: PlanType.FamiliesAnnually2025;
this._familyPlan = PlanType.FamiliesAnnually;
if (this.currentPlan && this.currentPlan.productTier !== ProductTierType.Enterprise) {
const upgradedPlan = this.passwordManagerPlans.find((plan) =>
this.currentPlan.productTier === ProductTierType.Free

View File

@ -738,26 +738,12 @@ describe("OrganizationPlansComponent", () => {
});
});
describe("feature flags", () => {
it("should use FamiliesAnnually when PM26462_Milestone_3 is enabled", async () => {
mockConfigService.getFeatureFlag.mockResolvedValue(true);
it("should use FamiliesAnnually as the family plan", async () => {
fixture.detectChanges();
await fixture.whenStable();
fixture.detectChanges();
await fixture.whenStable();
const familyPlan = component["_familyPlan"];
expect(familyPlan).toBe(PlanType.FamiliesAnnually);
});
it("should use FamiliesAnnually2025 when feature flag is disabled", async () => {
mockConfigService.getFeatureFlag.mockResolvedValue(false);
fixture.detectChanges();
await fixture.whenStable();
const familyPlan = component["_familyPlan"];
expect(familyPlan).toBe(PlanType.FamiliesAnnually2025);
});
const familyPlan = component["_familyPlan"];
expect(familyPlan).toBe(PlanType.FamiliesAnnually);
});
describe("initialPlan and initialProductTier inputs", () => {

View File

@ -537,7 +537,7 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy {
await this.loadPlanData();
}
this._familyPlan = await this.determineFamilyPlan();
this._familyPlan = PlanType.FamiliesAnnually;
const currentPlan = this.currentPlan();
if (currentPlan) {
@ -1176,13 +1176,6 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy {
return !plan.disabled && !plan.legacyYear;
}
private async determineFamilyPlan(): Promise<PlanType> {
const milestone3FeatureEnabled = await this.configService.getFeatureFlag(
FeatureFlag.PM26462_Milestone_3,
);
return milestone3FeatureEnabled ? PlanType.FamiliesAnnually : PlanType.FamiliesAnnually2025;
}
/**
* Loads existing organization, billing, and subscription data for the given organization ID
* and populates the form controls with the retrieved billing address.

View File

@ -293,21 +293,14 @@ export class CompleteTrialInitiationComponent implements OnInit, OnDestroy {
this.verticalStepper.previous();
}
async getPlanType() {
const milestone3FeatureEnabled = await this.configService.getFeatureFlag(
FeatureFlag.PM26462_Milestone_3,
);
const familyPlan = milestone3FeatureEnabled
? PlanType.FamiliesAnnually
: PlanType.FamiliesAnnually2025;
getPlanType() {
switch (this.productTier) {
case ProductTierType.Teams:
return PlanType.TeamsAnnually;
case ProductTierType.Enterprise:
return PlanType.EnterpriseAnnually;
case ProductTierType.Families:
return familyPlan;
return PlanType.FamiliesAnnually;
case ProductTierType.Free:
return PlanType.Free;
default:

View File

@ -8,7 +8,6 @@ import { AccountService } from "@bitwarden/common/auth/abstractions/account.serv
import { OrganizationBillingServiceAbstraction } from "@bitwarden/common/billing/abstractions";
import { PlanType } from "@bitwarden/common/billing/enums";
import { PlanResponse } from "@bitwarden/common/billing/models/response/plan.response";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { mockAccountInfoWith } from "@bitwarden/common/spec";
import { UserId } from "@bitwarden/common/types/guid";
import { PreviewInvoiceClient } from "@bitwarden/web-vault/app/billing/clients";
@ -20,10 +19,8 @@ describe("TrialBillingStepService", () => {
const mockOrganizationBillingService = mock<OrganizationBillingServiceAbstraction>();
const mockPreviewInvoiceClient = mock<PreviewInvoiceClient>();
const mockAccountService = mock<AccountService>();
const mockConfigService = mock<ConfigService>();
const mockFamiliesPlan = {
type: PlanType.FamiliesAnnually2025,
type: PlanType.FamiliesAnnually,
productTier: 4,
name: "Families",
isAnnual: true,
@ -47,12 +44,9 @@ describe("TrialBillingStepService", () => {
mockReset(mockOrganizationBillingService);
mockReset(mockPreviewInvoiceClient);
mockReset(mockAccountService);
mockReset(mockConfigService);
mockApiService.getPlans.mockResolvedValue({ data: [mockFamiliesPlan] } as any);
mockAccountService.activeAccount$ = of(mockAccount as any);
mockConfigService.getFeatureFlag.mockResolvedValue(false);
mockConfigService.getFeatureFlag$.mockReturnValue(of(false));
TestBed.configureTestingModule({
providers: [
@ -64,7 +58,6 @@ describe("TrialBillingStepService", () => {
},
{ provide: PreviewInvoiceClient, useValue: mockPreviewInvoiceClient },
{ provide: AccountService, useValue: mockAccountService },
{ provide: ConfigService, useValue: mockConfigService },
],
});
@ -137,26 +130,4 @@ describe("TrialBillingStepService", () => {
expect(call).not.toHaveProperty("coupons");
});
});
describe("getCosts — feature flag", () => {
it("uses PM26462_Milestone_3 feature flag when fetching plan type", async () => {
mockPreviewInvoiceClient.previewTaxForOrganizationSubscriptionPurchase.mockResolvedValue({
tax: 0,
total: 40,
});
await sut.getCosts("passwordManager", "families", "annually", {
country: "US",
postalCode: "12345",
} as any);
expect(
mockPreviewInvoiceClient.previewTaxForOrganizationSubscriptionPurchase,
).toHaveBeenCalledWith(
expect.objectContaining({ tier: "families", cadence: "annually" }),
expect.anything(),
undefined,
);
});
});
});

View File

@ -1,5 +1,5 @@
import { Injectable } from "@angular/core";
import { combineLatestWith, firstValueFrom, from, map, shareReplay } from "rxjs";
import { firstValueFrom, from, map, shareReplay } from "rxjs";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { OrganizationResponse } from "@bitwarden/common/admin-console/models/response/organization.response";
@ -10,8 +10,6 @@ import {
SubscriptionInformation,
} from "@bitwarden/common/billing/abstractions";
import { PaymentMethodType, PlanType } from "@bitwarden/common/billing/enums";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { PreviewInvoiceClient } from "@bitwarden/web-vault/app/billing/clients";
import {
BillingAddressControls,
@ -64,7 +62,6 @@ export class TrialBillingStepService {
private apiService: ApiService,
private organizationBillingService: OrganizationBillingServiceAbstraction,
private previewInvoiceClient: PreviewInvoiceClient,
private configService: ConfigService,
) {}
private plans$ = from(this.apiService.getPlans()).pipe(
@ -73,17 +70,10 @@ export class TrialBillingStepService {
getPrices$ = (product: Product, tier: Tier) =>
this.plans$.pipe(
combineLatestWith(this.configService.getFeatureFlag$(FeatureFlag.PM26462_Milestone_3)),
map(([plans, milestone3FeatureEnabled]) => {
map((plans) => {
switch (tier) {
case "families": {
const annually = plans.data.find(
(plan) =>
plan.type ===
(milestone3FeatureEnabled
? PlanType.FamiliesAnnually
: PlanType.FamiliesAnnually2025),
);
const annually = plans.data.find((plan) => plan.type === PlanType.FamiliesAnnually);
return {
annually: annually!.PasswordManager.basePrice,
};
@ -162,15 +152,9 @@ export class TrialBillingStepService {
): Promise<OrganizationResponse> => {
const getPlanType = async (tier: Tier, cadence: Cadence) => {
const plans = await firstValueFrom(this.plans$);
const milestone3FeatureEnabled = await this.configService.getFeatureFlag(
FeatureFlag.PM26462_Milestone_3,
);
const familyPlan = milestone3FeatureEnabled
? PlanType.FamiliesAnnually
: PlanType.FamiliesAnnually2025;
switch (tier) {
case "families":
return plans.data.find((plan) => plan.type === familyPlan)!.type;
return plans.data.find((plan) => plan.type === PlanType.FamiliesAnnually)!.type;
case "teams":
return plans.data.find(
(plan) =>

View File

@ -30,7 +30,7 @@ describe("DefaultSubscriptionPricingService", () => {
let environmentService: MockProxy<EnvironmentService>;
const mockFamiliesPlan = {
type: PlanType.FamiliesAnnually2025,
type: PlanType.FamiliesAnnually,
productTier: ProductTierType.Families,
name: "Families (Annually)",
isAnnual: true,

View File

@ -1,6 +1,5 @@
import {
combineLatest,
combineLatestWith,
from,
map,
Observable,
@ -16,7 +15,6 @@ import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstract
import { PlanType } from "@bitwarden/common/billing/enums";
import { PlanResponse } from "@bitwarden/common/billing/models/response/plan.response";
import { PremiumPlanResponse } from "@bitwarden/common/billing/models/response/premium-plan.response";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { ListResponse } from "@bitwarden/common/models/response/list.response";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
@ -137,13 +135,8 @@ export class DefaultSubscriptionPricingService implements SubscriptionPricingSer
private families$: Observable<PersonalSubscriptionPricingTier> =
this.organizationPlansResponse$.pipe(
combineLatestWith(this.configService.getFeatureFlag$(FeatureFlag.PM26462_Milestone_3)),
map(([plans, milestone3FeatureEnabled]) => {
const familiesPlan = plans.data.find(
(plan) =>
plan.type ===
(milestone3FeatureEnabled ? PlanType.FamiliesAnnually : PlanType.FamiliesAnnually2025),
);
map((plans) => {
const familiesPlan = plans.data.find((plan) => plan.type === PlanType.FamiliesAnnually);
return {
id: PersonalSubscriptionPricingTierIds.Families,