diff --git a/apps/web/src/app/admin-console/organizations/layouts/organization-layout.component.html b/apps/web/src/app/admin-console/organizations/layouts/organization-layout.component.html
index ef35b3c2b4..348c410075 100644
--- a/apps/web/src/app/admin-console/organizations/layouts/organization-layout.component.html
+++ b/apps/web/src/app/admin-console/organizations/layouts/organization-layout.component.html
@@ -115,10 +115,9 @@
>
{{ "accessingUsingProvider" | i18n: organization.providerName }}
-
+
diff --git a/apps/web/src/app/admin-console/organizations/layouts/organization-layout.component.ts b/apps/web/src/app/admin-console/organizations/layouts/organization-layout.component.ts
index efbcaf292d..81b1ca142b 100644
--- a/apps/web/src/app/admin-console/organizations/layouts/organization-layout.component.ts
+++ b/apps/web/src/app/admin-console/organizations/layouts/organization-layout.component.ts
@@ -15,10 +15,12 @@ import {
OrganizationService,
} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
+import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
+import { ConfigServiceAbstraction as ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { BannerModule, IconModule, LayoutComponent, NavigationModule } from "@bitwarden/components";
-import { PaymentMethodBannersComponent } from "../../../components/payment-method-banners/payment-method-banners.component";
+import { PaymentMethodWarningsModule } from "../../../billing/shared";
import { OrgSwitcherComponent } from "../../../layouts/org-switcher/org-switcher.component";
import { AdminConsoleLogo } from "../../icons/admin-console-logo";
@@ -35,7 +37,7 @@ import { AdminConsoleLogo } from "../../icons/admin-console-logo";
NavigationModule,
OrgSwitcherComponent,
BannerModule,
- PaymentMethodBannersComponent,
+ PaymentMethodWarningsModule,
],
})
export class OrganizationLayoutComponent implements OnInit, OnDestroy {
@@ -48,10 +50,16 @@ export class OrganizationLayoutComponent implements OnInit, OnDestroy {
private _destroy = new Subject();
+ protected showPaymentMethodWarningBanners$ = this.configService.getFeatureFlag$(
+ FeatureFlag.ShowPaymentMethodWarningBanners,
+ false,
+ );
+
constructor(
private route: ActivatedRoute,
private organizationService: OrganizationService,
private platformUtilsService: PlatformUtilsService,
+ private configService: ConfigService,
) {}
async ngOnInit() {
diff --git a/apps/web/src/app/app.component.ts b/apps/web/src/app/app.component.ts
index f9479a92ce..77f6fd6f1f 100644
--- a/apps/web/src/app/app.component.ts
+++ b/apps/web/src/app/app.component.ts
@@ -4,16 +4,18 @@ import { DomSanitizer } from "@angular/platform-browser";
import { NavigationEnd, Router } from "@angular/router";
import * as jq from "jquery";
import { IndividualConfig, ToastrService } from "ngx-toastr";
-import { Subject, takeUntil } from "rxjs";
+import { Subject, switchMap, takeUntil, timer } from "rxjs";
import { EventUploadService } from "@bitwarden/common/abstractions/event/event-upload.service";
import { NotificationsService } from "@bitwarden/common/abstractions/notifications.service";
import { SearchService } from "@bitwarden/common/abstractions/search.service";
import { SettingsService } from "@bitwarden/common/abstractions/settings.service";
import { VaultTimeoutService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout.service";
+import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { InternalPolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
import { KeyConnectorService } from "@bitwarden/common/auth/abstractions/key-connector.service";
+import { PaymentMethodWarningsServiceAbstraction as PaymentMethodWarningService } from "@bitwarden/common/billing/abstractions/payment-method-warnings-service.abstraction";
import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service";
import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
@@ -45,6 +47,7 @@ import { RouterService } from "./core";
const BroadcasterSubscriptionId = "AppComponent";
const IdleTimeout = 60000 * 10; // 10 minutes
+const PaymentMethodWarningsRefresh = 60000; // 1 Minute
@Component({
selector: "app-root",
@@ -55,6 +58,7 @@ export class AppComponent implements OnDestroy, OnInit {
private idleTimer: number = null;
private isIdle = false;
private destroy$ = new Subject();
+ private paymentMethodWarningsRefresh$ = timer(0, PaymentMethodWarningsRefresh);
constructor(
@Inject(DOCUMENT) private document: Document,
@@ -85,6 +89,8 @@ export class AppComponent implements OnDestroy, OnInit {
private configService: ConfigServiceAbstraction,
private dialogService: DialogService,
private biometricStateService: BiometricStateService,
+ private paymentMethodWarningService: PaymentMethodWarningService,
+ private organizationService: OrganizationService,
) {}
ngOnInit() {
@@ -238,6 +244,21 @@ export class AppComponent implements OnDestroy, OnInit {
new DisableSendPolicy(),
new SendOptionsPolicy(),
]);
+
+ this.paymentMethodWarningsRefresh$
+ .pipe(
+ switchMap(() => this.organizationService.memberOrganizations$),
+ switchMap(
+ async (organizations) =>
+ await Promise.all(
+ organizations.map((organization) =>
+ this.paymentMethodWarningService.update(organization.id),
+ ),
+ ),
+ ),
+ takeUntil(this.destroy$),
+ )
+ .subscribe();
}
ngOnDestroy() {
@@ -260,6 +281,7 @@ export class AppComponent implements OnDestroy, OnInit {
this.passwordGenerationService.clear(),
this.keyConnectorService.clear(),
this.biometricStateService.logout(userId as UserId),
+ this.paymentMethodWarningService.clear(),
]);
this.searchService.clearIndex();
diff --git a/apps/web/src/app/billing/shared/adjust-payment.component.ts b/apps/web/src/app/billing/shared/adjust-payment.component.ts
index 595566b11d..7452344141 100644
--- a/apps/web/src/app/billing/shared/adjust-payment.component.ts
+++ b/apps/web/src/app/billing/shared/adjust-payment.component.ts
@@ -2,7 +2,7 @@ import { Component, EventEmitter, Input, Output, ViewChild } from "@angular/core
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
-import { BillingBannerServiceAbstraction } from "@bitwarden/common/billing/abstractions/billing-banner.service.abstraction";
+import { PaymentMethodWarningsServiceAbstraction as PaymentMethodWarningService } from "@bitwarden/common/billing/abstractions/payment-method-warnings-service.abstraction";
import { PaymentMethodType } from "@bitwarden/common/billing/enums";
import { PaymentRequest } from "@bitwarden/common/billing/models/request/payment.request";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
@@ -34,7 +34,7 @@ export class AdjustPaymentComponent {
private platformUtilsService: PlatformUtilsService,
private logService: LogService,
private organizationApiService: OrganizationApiServiceAbstraction,
- private billingBannerService: BillingBannerServiceAbstraction,
+ private paymentMethodWarningService: PaymentMethodWarningService,
) {}
async submit() {
@@ -59,7 +59,7 @@ export class AdjustPaymentComponent {
});
await this.formPromise;
if (this.organizationId) {
- await this.billingBannerService.setPaymentMethodBannerState(this.organizationId, false);
+ await this.paymentMethodWarningService.removeSubscriptionRisk(this.organizationId);
}
this.platformUtilsService.showToast(
"success",
diff --git a/apps/web/src/app/billing/shared/index.ts b/apps/web/src/app/billing/shared/index.ts
index ae28e45f78..edaf3a1199 100644
--- a/apps/web/src/app/billing/shared/index.ts
+++ b/apps/web/src/app/billing/shared/index.ts
@@ -3,3 +3,4 @@ export * from "./payment-method.component";
export * from "./payment.component";
export * from "./sm-subscribe.component";
export * from "./tax-info.component";
+export * from "./payment-method-warnings/payment-method-warnings.module";
diff --git a/apps/web/src/app/billing/shared/payment-method-warnings/payment-method-warnings.component.html b/apps/web/src/app/billing/shared/payment-method-warnings/payment-method-warnings.component.html
new file mode 100644
index 0000000000..59dbc5f976
--- /dev/null
+++ b/apps/web/src/app/billing/shared/payment-method-warnings/payment-method-warnings.component.html
@@ -0,0 +1,15 @@
+
+
+ {{ "maintainYourSubscription" | i18n: warning.organizationName }}
+ {{ "addAPaymentMethod" | i18n }}.
+
+
diff --git a/apps/web/src/app/billing/shared/payment-method-warnings/payment-method-warnings.component.ts b/apps/web/src/app/billing/shared/payment-method-warnings/payment-method-warnings.component.ts
new file mode 100644
index 0000000000..d811961c0d
--- /dev/null
+++ b/apps/web/src/app/billing/shared/payment-method-warnings/payment-method-warnings.component.ts
@@ -0,0 +1,33 @@
+import { Component } from "@angular/core";
+import { map, Observable } from "rxjs";
+
+import { PaymentMethodWarningsServiceAbstraction as PaymentMethodWarningService } from "@bitwarden/common/billing/abstractions/payment-method-warnings-service.abstraction";
+
+type Warning = {
+ organizationId: string;
+ organizationName: string;
+};
+
+@Component({
+ selector: "app-payment-method-warnings",
+ templateUrl: "payment-method-warnings.component.html",
+})
+export class PaymentMethodWarningsComponent {
+ constructor(private paymentMethodWarningService: PaymentMethodWarningService) {}
+
+ protected warnings$: Observable =
+ this.paymentMethodWarningService.paymentMethodWarnings$.pipe(
+ map((warnings) =>
+ Object.entries(warnings ?? [])
+ .filter(([_, warning]) => warning.risksSubscriptionFailure && !warning.acknowledged)
+ .map(([organizationId, { organizationName }]) => ({
+ organizationId,
+ organizationName,
+ })),
+ ),
+ );
+
+ protected async closeWarning(organizationId: string): Promise {
+ await this.paymentMethodWarningService.acknowledge(organizationId);
+ }
+}
diff --git a/apps/web/src/app/billing/shared/payment-method-warnings/payment-method-warnings.module.ts b/apps/web/src/app/billing/shared/payment-method-warnings/payment-method-warnings.module.ts
new file mode 100644
index 0000000000..c6303c878c
--- /dev/null
+++ b/apps/web/src/app/billing/shared/payment-method-warnings/payment-method-warnings.module.ts
@@ -0,0 +1,14 @@
+import { NgModule } from "@angular/core";
+
+import { BannerModule } from "@bitwarden/components";
+
+import { SharedModule } from "../../../shared";
+
+import { PaymentMethodWarningsComponent } from "./payment-method-warnings.component";
+
+@NgModule({
+ imports: [BannerModule, SharedModule],
+ declarations: [PaymentMethodWarningsComponent],
+ exports: [PaymentMethodWarningsComponent],
+})
+export class PaymentMethodWarningsModule {}
diff --git a/apps/web/src/app/components/payment-method-banners/payment-method-banners.component.html b/apps/web/src/app/components/payment-method-banners/payment-method-banners.component.html
deleted file mode 100644
index 1820525ee8..0000000000
--- a/apps/web/src/app/components/payment-method-banners/payment-method-banners.component.html
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
- {{ "maintainYourSubscription" | i18n: banner.organizationName }}
- {{ "addAPaymentMethod" | i18n }}.
-
-
diff --git a/apps/web/src/app/components/payment-method-banners/payment-method-banners.component.ts b/apps/web/src/app/components/payment-method-banners/payment-method-banners.component.ts
deleted file mode 100644
index 1a7e3730db..0000000000
--- a/apps/web/src/app/components/payment-method-banners/payment-method-banners.component.ts
+++ /dev/null
@@ -1,76 +0,0 @@
-import { Component } from "@angular/core";
-import { combineLatest, Observable, switchMap } from "rxjs";
-
-import { OrganizationApiServiceAbstraction as OrganizationApiService } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
-import {
- canAccessAdmin,
- OrganizationService,
-} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
-import { BillingBannerServiceAbstraction } from "@bitwarden/common/billing/abstractions/billing-banner.service.abstraction";
-import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
-import { BannerModule } from "@bitwarden/components";
-
-import { SharedModule } from "../../shared/shared.module";
-
-type PaymentMethodBannerData = {
- organizationId: string;
- organizationName: string;
- visible: boolean;
-};
-
-@Component({
- standalone: true,
- selector: "app-payment-method-banners",
- templateUrl: "payment-method-banners.component.html",
- imports: [BannerModule, SharedModule],
-})
-export class PaymentMethodBannersComponent {
- constructor(
- private billingBannerService: BillingBannerServiceAbstraction,
- private i18nService: I18nService,
- private organizationService: OrganizationService,
- private organizationApiService: OrganizationApiService,
- ) {}
-
- private organizations$ = this.organizationService.memberOrganizations$.pipe(
- canAccessAdmin(this.i18nService),
- );
-
- protected banners$: Observable = combineLatest([
- this.organizations$,
- this.billingBannerService.paymentMethodBannerStates$,
- ]).pipe(
- switchMap(async ([organizations, paymentMethodBannerStates]) => {
- return await Promise.all(
- organizations.map(async (organization) => {
- const matchingBanner = paymentMethodBannerStates.find(
- (banner) => banner.organizationId === organization.id,
- );
- if (matchingBanner !== null && matchingBanner !== undefined) {
- return {
- organizationId: organization.id,
- organizationName: organization.name,
- visible: matchingBanner.visible,
- };
- }
- const response = await this.organizationApiService.risksSubscriptionFailure(
- organization.id,
- );
- await this.billingBannerService.setPaymentMethodBannerState(
- organization.id,
- response.risksSubscriptionFailure,
- );
- return {
- organizationId: organization.id,
- organizationName: organization.name,
- visible: response.risksSubscriptionFailure,
- };
- }),
- );
- }),
- );
-
- protected async closeBanner(organizationId: string): Promise {
- await this.billingBannerService.setPaymentMethodBannerState(organizationId, false);
- }
-}
diff --git a/apps/web/src/app/layouts/user-layout.component.html b/apps/web/src/app/layouts/user-layout.component.html
index 3593390113..397e95d485 100644
--- a/apps/web/src/app/layouts/user-layout.component.html
+++ b/apps/web/src/app/layouts/user-layout.component.html
@@ -33,9 +33,8 @@
>
-
+
diff --git a/apps/web/src/app/layouts/user-layout.component.ts b/apps/web/src/app/layouts/user-layout.component.ts
index 886069c580..1f116ee76e 100644
--- a/apps/web/src/app/layouts/user-layout.component.ts
+++ b/apps/web/src/app/layouts/user-layout.component.ts
@@ -5,13 +5,15 @@ import { RouterModule } from "@angular/router";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
+import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service";
+import { ConfigServiceAbstraction as ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
import { IconModule, LayoutComponent, NavigationModule } from "@bitwarden/components";
-import { PaymentMethodBannersComponent } from "../components/payment-method-banners/payment-method-banners.component";
+import { PaymentMethodWarningsModule } from "../billing/shared";
import { PasswordManagerLogo } from "./password-manager-logo";
@@ -28,7 +30,7 @@ const BroadcasterSubscriptionId = "UserLayoutComponent";
LayoutComponent,
IconModule,
NavigationModule,
- PaymentMethodBannersComponent,
+ PaymentMethodWarningsModule,
],
})
export class UserLayoutComponent implements OnInit, OnDestroy {
@@ -36,6 +38,11 @@ export class UserLayoutComponent implements OnInit, OnDestroy {
hasFamilySponsorshipAvailable: boolean;
hideSubscription: boolean;
+ protected showPaymentMethodWarningBanners$ = this.configService.getFeatureFlag$(
+ FeatureFlag.ShowPaymentMethodWarningBanners,
+ false,
+ );
+
constructor(
private broadcasterService: BroadcasterService,
private ngZone: NgZone,
@@ -44,6 +51,7 @@ export class UserLayoutComponent implements OnInit, OnDestroy {
private stateService: StateService,
private apiService: ApiService,
private syncService: SyncService,
+ private configService: ConfigService,
) {}
async ngOnInit() {
diff --git a/apps/web/src/app/shared/loose-components.module.ts b/apps/web/src/app/shared/loose-components.module.ts
index 770a862eab..586f207962 100644
--- a/apps/web/src/app/shared/loose-components.module.ts
+++ b/apps/web/src/app/shared/loose-components.module.ts
@@ -58,8 +58,8 @@ import { UpdatePasswordComponent } from "../auth/update-password.component";
import { UpdateTempPasswordComponent } from "../auth/update-temp-password.component";
import { VerifyEmailTokenComponent } from "../auth/verify-email-token.component";
import { VerifyRecoverDeleteComponent } from "../auth/verify-recover-delete.component";
+import { PaymentMethodWarningsModule } from "../billing/shared";
import { DynamicAvatarComponent } from "../components/dynamic-avatar.component";
-import { PaymentMethodBannersComponent } from "../components/payment-method-banners/payment-method-banners.component";
import { SelectableAvatarComponent } from "../components/selectable-avatar.component";
import { FrontendLayoutComponent } from "../layouts/frontend-layout.component";
import { HeaderModule } from "../layouts/header/header.module";
@@ -106,12 +106,12 @@ import { SharedModule } from "./shared.module";
PipesModule,
PasswordCalloutComponent,
DangerZoneComponent,
- PaymentMethodBannersComponent,
LayoutComponent,
NavigationModule,
HeaderModule,
OrganizationLayoutComponent,
UserLayoutComponent,
+ PaymentMethodWarningsModule,
],
declarations: [
AcceptFamilySponsorshipComponent,
diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/providers-layout.component.html b/bitwarden_license/bit-web/src/app/admin-console/providers/providers-layout.component.html
index 4748374511..333ea66e26 100644
--- a/bitwarden_license/bit-web/src/app/admin-console/providers/providers-layout.component.html
+++ b/bitwarden_license/bit-web/src/app/admin-console/providers/providers-layout.component.html
@@ -24,9 +24,8 @@
*ngIf="showSettingsTab"
>
-
+
diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/providers-layout.component.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/providers-layout.component.ts
index 3ed9acf4d2..5f45379442 100644
--- a/bitwarden_license/bit-web/src/app/admin-console/providers/providers-layout.component.ts
+++ b/bitwarden_license/bit-web/src/app/admin-console/providers/providers-layout.component.ts
@@ -5,9 +5,11 @@ import { ActivatedRoute, RouterModule } from "@angular/router";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service";
import { Provider } from "@bitwarden/common/admin-console/models/domain/provider";
+import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
+import { ConfigServiceAbstraction as ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction";
import { IconModule, LayoutComponent, NavigationModule } from "@bitwarden/components";
import { ProviderPortalLogo } from "@bitwarden/web-vault/app/admin-console/icons/provider-portal-logo";
-import { PaymentMethodBannersComponent } from "@bitwarden/web-vault/app/components/payment-method-banners/payment-method-banners.component";
+import { PaymentMethodWarningsModule } from "@bitwarden/web-vault/app/billing/shared";
@Component({
selector: "providers-layout",
@@ -20,7 +22,7 @@ import { PaymentMethodBannersComponent } from "@bitwarden/web-vault/app/componen
LayoutComponent,
IconModule,
NavigationModule,
- PaymentMethodBannersComponent,
+ PaymentMethodWarningsModule,
],
})
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
@@ -30,9 +32,15 @@ export class ProvidersLayoutComponent {
provider: Provider;
private providerId: string;
+ protected showPaymentMethodWarningBanners$ = this.configService.getFeatureFlag$(
+ FeatureFlag.ShowPaymentMethodWarningBanners,
+ false,
+ );
+
constructor(
private route: ActivatedRoute,
private providerService: ProviderService,
+ private configService: ConfigService,
) {}
ngOnInit() {
diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/providers.module.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/providers.module.ts
index 2a6de3137c..512053d032 100644
--- a/bitwarden_license/bit-web/src/app/admin-console/providers/providers.module.ts
+++ b/bitwarden_license/bit-web/src/app/admin-console/providers/providers.module.ts
@@ -5,7 +5,7 @@ import { FormsModule } from "@angular/forms";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { SearchModule } from "@bitwarden/components";
import { OrganizationPlansComponent } from "@bitwarden/web-vault/app/billing";
-import { PaymentMethodBannersComponent } from "@bitwarden/web-vault/app/components/payment-method-banners/payment-method-banners.component";
+import { PaymentMethodWarningsModule } from "@bitwarden/web-vault/app/billing/shared";
import { OssModule } from "@bitwarden/web-vault/app/oss.module";
import { AddOrganizationComponent } from "./clients/add-organization.component";
@@ -33,9 +33,9 @@ import { SetupComponent } from "./setup/setup.component";
JslibModule,
ProvidersRoutingModule,
OrganizationPlansComponent,
- PaymentMethodBannersComponent,
SearchModule,
ProvidersLayoutComponent,
+ PaymentMethodWarningsModule,
],
declarations: [
AcceptProviderComponent,
diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup.component.html b/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup.component.html
index 80dee03174..1e7146bb58 100644
--- a/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup.component.html
+++ b/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup.component.html
@@ -1,4 +1,6 @@
-
+