diff --git a/apps/web/src/app/admin-console/organizations/collections/deprecated_vault.component.html b/apps/web/src/app/admin-console/organizations/collections/deprecated_vault.component.html
index 63dcbecf91e..326dc627e17 100644
--- a/apps/web/src/app/admin-console/organizations/collections/deprecated_vault.component.html
+++ b/apps/web/src/app/admin-console/organizations/collections/deprecated_vault.component.html
@@ -1,45 +1,12 @@
-
-
-
-
-
-
- {{ freeTrial.message }}
-
- {{ "clickHereToAddPaymentMethod" | i18n }}
-
-
-
-
-
- {{ resellerWarning?.message }}
-
-
+
+
+
+}
| undefined;
protected isEmpty: boolean;
protected showCollectionAccessRestricted: boolean;
- private hasSubscription$ = new BehaviorSubject(false);
protected currentSearchText$: Observable;
- protected useOrganizationWarningsService$: Observable;
- protected freeTrialWhenWarningsServiceDisabled$: Observable;
- protected resellerWarningWhenWarningsServiceDisabled$: Observable;
protected prevCipherId: string | null = null;
protected userId: UserId;
/**
@@ -209,31 +194,6 @@ export class VaultComponent implements OnInit, OnDestroy {
@ViewChild("vaultItems", { static: false }) vaultItemsComponent: VaultItemsComponent;
- private readonly unpaidSubscriptionDialog$ = this.accountService.activeAccount$.pipe(
- map((account) => account?.id),
- switchMap((id) =>
- this.organizationService.organizations$(id).pipe(
- filter((organizations) => organizations.length === 1),
- map(([organization]) => organization),
- switchMap((organization) =>
- from(this.billingApiService.getOrganizationBillingMetadata(organization.id)).pipe(
- tap((organizationMetaData) => {
- this.hasSubscription$.next(organizationMetaData.hasSubscription);
- }),
- switchMap((organizationMetaData) =>
- from(
- this.trialFlowService.handleUnpaidSubscriptionDialog(
- organization,
- organizationMetaData,
- ),
- ),
- ),
- ),
- ),
- ),
- ),
- );
-
constructor(
private route: ActivatedRoute,
private organizationService: OrganizationService,
@@ -262,13 +222,8 @@ export class VaultComponent implements OnInit, OnDestroy {
private toastService: ToastService,
private configService: ConfigService,
private cipherFormConfigService: CipherFormConfigService,
- private organizationApiService: OrganizationApiServiceAbstraction,
- private trialFlowService: TrialFlowService,
protected billingApiService: BillingApiServiceAbstraction,
- private organizationBillingService: OrganizationBillingServiceAbstraction,
- private resellerWarningService: ResellerWarningService,
private accountService: AccountService,
- private billingNotificationService: BillingNotificationService,
private organizationWarningsService: OrganizationWarningsService,
private collectionService: CollectionService,
) {}
@@ -661,74 +616,17 @@ export class VaultComponent implements OnInit, OnDestroy {
.subscribe();
// Billing Warnings
- this.useOrganizationWarningsService$ = this.configService.getFeatureFlag$(
- FeatureFlag.UseOrganizationWarningsService,
- );
-
- this.useOrganizationWarningsService$
- .pipe(
- switchMap((enabled) =>
- enabled
- ? this.organizationWarningsService.showInactiveSubscriptionDialog$(this.organization)
- : this.unpaidSubscriptionDialog$,
- ),
- takeUntil(this.destroy$),
- )
- .subscribe();
-
organization$
.pipe(
switchMap((organization) =>
- this.organizationWarningsService.showSubscribeBeforeFreeTrialEndsDialog$(organization),
+ merge(
+ this.organizationWarningsService.showInactiveSubscriptionDialog$(organization),
+ this.organizationWarningsService.showSubscribeBeforeFreeTrialEndsDialog$(organization),
+ ),
),
takeUntil(this.destroy$),
)
.subscribe();
-
- const freeTrial$ = combineLatest([
- organization$,
- this.hasSubscription$.pipe(filter((hasSubscription) => hasSubscription !== null)),
- ]).pipe(
- filter(
- ([org, hasSubscription]) => org.isOwner && hasSubscription && org.canViewBillingHistory,
- ),
- switchMap(([org]) =>
- combineLatest([
- of(org),
- this.organizationApiService.getSubscription(org.id),
- from(this.organizationBillingService.getPaymentSource(org.id)).pipe(
- catchError((error: unknown) => {
- this.billingNotificationService.handleError(error);
- return of(null);
- }),
- ),
- ]),
- ),
- map(([org, sub, paymentSource]) =>
- this.trialFlowService.checkForOrgsWithUpcomingPaymentIssues(org, sub, paymentSource),
- ),
- filter((result) => result !== null),
- );
-
- this.freeTrialWhenWarningsServiceDisabled$ = this.useOrganizationWarningsService$.pipe(
- filter((enabled) => !enabled),
- switchMap(() => freeTrial$),
- );
-
- const resellerWarning$ = organization$.pipe(
- filter((org) => org.isOwner),
- switchMap((org) =>
- from(this.billingApiService.getOrganizationBillingMetadata(org.id)).pipe(
- map((metadata) => ({ org, metadata })),
- ),
- ),
- map(({ org, metadata }) => this.resellerWarningService.getWarning(org, metadata)),
- );
-
- this.resellerWarningWhenWarningsServiceDisabled$ = this.useOrganizationWarningsService$.pipe(
- filter((enabled) => !enabled),
- switchMap(() => resellerWarning$),
- );
// End Billing Warnings
firstSetup$
diff --git a/apps/web/src/app/admin-console/organizations/collections/vault.component.html b/apps/web/src/app/admin-console/organizations/collections/vault.component.html
index f2a4e4f4585..b9e27b026c5 100644
--- a/apps/web/src/app/admin-console/organizations/collections/vault.component.html
+++ b/apps/web/src/app/admin-console/organizations/collections/vault.component.html
@@ -5,47 +5,16 @@
@let loading = loading$ | async;
@if (organization) {
- @if (useOrganizationWarningsService$ | async) {
+ @if (!refreshing) {
- }
-
- @if (useOrganizationWarningsService$ | async) {
}
- @let freeTrial = freeTrialWhenWarningsServiceDisabled$ | async;
- @if (!refreshing && freeTrial?.shownBanner) {
-
- {{ freeTrial.message }}
-
- {{ "clickHereToAddPaymentMethod" | i18n }}
-
-
- }
-
- @let resellerWarning = resellerWarningWhenWarningsServiceDisabled$ | async;
- @if (!refreshing && resellerWarning) {
-
- {{ resellerWarning?.message }}
-
- }
-
@if (filter) {
;
protected isEmpty$: Observable = of(false);
- private hasSubscription$ = new BehaviorSubject(false);
- protected useOrganizationWarningsService$: Observable;
- protected freeTrialWhenWarningsServiceDisabled$: Observable;
- protected resellerWarningWhenWarningsServiceDisabled$: Observable;
protected prevCipherId: string | null = null;
protected userId$: Observable;
@@ -227,31 +213,6 @@ export class vNextVaultComponent implements OnInit, OnDestroy {
| VaultItemsComponent
| undefined;
- private readonly unpaidSubscriptionDialog$ = this.accountService.activeAccount$.pipe(
- getUserId,
- switchMap((id) =>
- this.organizationService.organizations$(id).pipe(
- filter((organizations) => organizations.length === 1),
- map(([organization]) => organization),
- switchMap((organization) =>
- from(this.billingApiService.getOrganizationBillingMetadata(organization.id)).pipe(
- tap((organizationMetaData) => {
- this.hasSubscription$.next(organizationMetaData.hasSubscription);
- }),
- switchMap((organizationMetaData) =>
- from(
- this.trialFlowService.handleUnpaidSubscriptionDialog(
- organization,
- organizationMetaData,
- ),
- ),
- ),
- ),
- ),
- ),
- ),
- );
-
constructor(
private route: ActivatedRoute,
private organizationService: OrganizationService,
@@ -280,13 +241,8 @@ export class vNextVaultComponent implements OnInit, OnDestroy {
private toastService: ToastService,
private configService: ConfigService,
private cipherFormConfigService: CipherFormConfigService,
- private organizationApiService: OrganizationApiServiceAbstraction,
- private trialFlowService: TrialFlowService,
protected billingApiService: BillingApiServiceAbstraction,
- private organizationBillingService: OrganizationBillingServiceAbstraction,
- private resellerWarningService: ResellerWarningService,
private accountService: AccountService,
- private billingNotificationService: BillingNotificationService,
private organizationWarningsService: OrganizationWarningsService,
private collectionService: CollectionService,
private restrictedItemTypesService: RestrictedItemTypesService,
@@ -461,68 +417,17 @@ export class vNextVaultComponent implements OnInit, OnDestroy {
);
// Billing Warnings
- this.useOrganizationWarningsService$ = this.configService.getFeatureFlag$(
- FeatureFlag.UseOrganizationWarningsService,
- );
-
- const freeTrial$ = combineLatest([
- this.organization$,
- this.hasSubscription$.pipe(filter((hasSubscription) => hasSubscription !== null)),
- ]).pipe(
- filter(
- ([org, hasSubscription]) => org.isOwner && hasSubscription && org.canViewBillingHistory,
- ),
- switchMap(([org]) =>
- combineLatest([
- of(org),
- this.organizationApiService.getSubscription(org.id),
- from(this.organizationBillingService.getPaymentSource(org.id)).pipe(
- map((paymentSource) => {
- if (paymentSource == null) {
- throw new Error("Payment source not found.");
- }
- return paymentSource;
- }),
- ),
- ]),
- ),
- map(([org, sub, paymentSource]) =>
- this.trialFlowService.checkForOrgsWithUpcomingPaymentIssues(org, sub, paymentSource),
- ),
- filter((result) => result !== null),
- catchError((error: unknown) => {
- this.billingNotificationService.handleError(error);
- return of();
- }),
- );
-
- this.freeTrialWhenWarningsServiceDisabled$ = this.useOrganizationWarningsService$.pipe(
- filter((enabled) => !enabled),
- switchMap(() => freeTrial$),
- );
-
- this.resellerWarningWhenWarningsServiceDisabled$ = combineLatest([
- this.organization$,
- this.useOrganizationWarningsService$,
- ]).pipe(
- filter(([org, enabled]) => !enabled && org.isOwner),
- switchMap(([org]) =>
- from(this.billingApiService.getOrganizationBillingMetadata(org.id)).pipe(
- map((metadata) => ({ org, metadata })),
- ),
- ),
- map(({ org, metadata }) => this.resellerWarningService.getWarning(org, metadata)),
- );
-
this.organization$
.pipe(
switchMap((organization) =>
- this.organizationWarningsService.showSubscribeBeforeFreeTrialEndsDialog$(organization),
+ merge(
+ this.organizationWarningsService.showInactiveSubscriptionDialog$(organization),
+ this.organizationWarningsService.showSubscribeBeforeFreeTrialEndsDialog$(organization),
+ ),
),
takeUntilDestroyed(),
)
.subscribe();
-
// End Billing Warnings
this.editableCollections$ = combineLatest([
@@ -781,17 +686,6 @@ export class vNextVaultComponent implements OnInit, OnDestroy {
)
.subscribe();
- combineLatest([this.useOrganizationWarningsService$, this.organization$])
- .pipe(
- switchMap(([enabled, organization]) =>
- enabled
- ? this.organizationWarningsService.showInactiveSubscriptionDialog$(organization)
- : this.unpaidSubscriptionDialog$,
- ),
- takeUntil(this.destroy$),
- )
- .subscribe();
-
// Handle last of initial setup - workaround for some state issues where we need to manually
// push the collections we've loaded back into the VaultFilterService.
// FIXME: figure out how we can remove this.
diff --git a/apps/web/src/app/billing/organizations/payment-details/organization-payment-details.component.ts b/apps/web/src/app/billing/organizations/payment-details/organization-payment-details.component.ts
index 05802d5868a..47742ba0a88 100644
--- a/apps/web/src/app/billing/organizations/payment-details/organization-payment-details.component.ts
+++ b/apps/web/src/app/billing/organizations/payment-details/organization-payment-details.component.ts
@@ -243,7 +243,6 @@ export class OrganizationPaymentDetailsComponent implements OnInit, OnDestroy {
const result = await lastValueFrom(dialogRef.closed);
if (result?.type === "success") {
await this.setPaymentMethod(result.paymentMethod);
- this.organizationWarningsService.refreshFreeTrialWarning();
}
};
@@ -264,6 +263,10 @@ export class OrganizationPaymentDetailsComponent implements OnInit, OnDestroy {
setPaymentMethod = async (paymentMethod: MaskedPaymentMethod) => {
if (this.viewState$.value) {
+ if (!this.viewState$.value.paymentMethod) {
+ this.organizationWarningsService.refreshFreeTrialWarning();
+ }
+
const billingAddress =
this.viewState$.value.billingAddress ??
(await this.subscriberBillingClient.getBillingAddress(this.viewState$.value.organization));
diff --git a/apps/web/src/app/billing/organizations/payment-method/organization-payment-method.component.html b/apps/web/src/app/billing/organizations/payment-method/organization-payment-method.component.html
index cf3fe8a9338..ab31147e916 100644
--- a/apps/web/src/app/billing/organizations/payment-method/organization-payment-method.component.html
+++ b/apps/web/src/app/billing/organizations/payment-method/organization-payment-method.component.html
@@ -1,21 +1,3 @@
-
- {{ freeTrialData?.message }}
-
- {{ "clickHereToAddPaymentMethod" | i18n }}
-
-
diff --git a/apps/web/src/app/billing/organizations/payment-method/organization-payment-method.component.ts b/apps/web/src/app/billing/organizations/payment-method/organization-payment-method.component.ts
index 855263cdbc1..4106ee4f9cd 100644
--- a/apps/web/src/app/billing/organizations/payment-method/organization-payment-method.component.ts
+++ b/apps/web/src/app/billing/organizations/payment-method/organization-payment-method.component.ts
@@ -6,8 +6,8 @@ import { combineLatest, firstValueFrom, from, lastValueFrom, map, switchMap } fr
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
import {
- getOrganizationById,
OrganizationService,
+ getOrganizationById,
} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
@@ -25,7 +25,6 @@ import { SyncService } from "@bitwarden/common/platform/sync";
import { DialogService, ToastService } from "@bitwarden/components";
import { BillingNotificationService } from "../../services/billing-notification.service";
-import { TrialFlowService } from "../../services/trial-flow.service";
import {
AddCreditDialogResult,
openAddCreditDialog,
@@ -38,7 +37,6 @@ import {
TRIAL_PAYMENT_METHOD_DIALOG_RESULT_TYPE,
TrialPaymentDialogComponent,
} from "../../shared/trial-payment-dialog/trial-payment-dialog.component";
-import { FreeTrial } from "../../types/free-trial";
@Component({
templateUrl: "./organization-payment-method.component.html",
@@ -50,7 +48,6 @@ export class OrganizationPaymentMethodComponent implements OnDestroy {
accountCredit?: number;
paymentSource?: PaymentSourceResponse;
subscriptionStatus?: string;
- protected freeTrialData?: FreeTrial;
organization?: Organization;
organizationSubscriptionResponse?: OrganizationSubscriptionResponse;
@@ -71,7 +68,6 @@ export class OrganizationPaymentMethodComponent implements OnDestroy {
private router: Router,
private toastService: ToastService,
private location: Location,
- private trialFlowService: TrialFlowService,
private organizationService: OrganizationService,
private accountService: AccountService,
protected syncService: SyncService,
@@ -183,12 +179,6 @@ export class OrganizationPaymentMethodComponent implements OnDestroy {
if (!this.paymentSource) {
throw new Error("Payment source is not found");
}
-
- this.freeTrialData = this.trialFlowService.checkForOrgsWithUpcomingPaymentIssues(
- this.organization,
- this.organizationSubscriptionResponse,
- this.paymentSource,
- );
}
// If the flag `launchPaymentModalAutomatically` is set to true,
// we schedule a timeout (delay of 800ms) to automatically launch the payment modal.
diff --git a/apps/web/src/app/billing/organizations/warnings/components/organization-free-trial-warning.component.ts b/apps/web/src/app/billing/organizations/warnings/components/organization-free-trial-warning.component.ts
index 1a889ec9b63..8390e432236 100644
--- a/apps/web/src/app/billing/organizations/warnings/components/organization-free-trial-warning.component.ts
+++ b/apps/web/src/app/billing/organizations/warnings/components/organization-free-trial-warning.component.ts
@@ -37,6 +37,7 @@ import { OrganizationFreeTrialWarning } from "../types";
})
export class OrganizationFreeTrialWarningComponent implements OnInit {
@Input({ required: true }) organization!: Organization;
+ @Input() includeOrganizationNameInMessaging = false;
@Output() clicked = new EventEmitter();
warning$!: Observable;
@@ -44,6 +45,9 @@ export class OrganizationFreeTrialWarningComponent implements OnInit {
constructor(private organizationWarningsService: OrganizationWarningsService) {}
ngOnInit() {
- this.warning$ = this.organizationWarningsService.getFreeTrialWarning$(this.organization);
+ this.warning$ = this.organizationWarningsService.getFreeTrialWarning$(
+ this.organization,
+ this.includeOrganizationNameInMessaging,
+ );
}
}
diff --git a/apps/web/src/app/billing/organizations/warnings/services/organization-warnings.service.ts b/apps/web/src/app/billing/organizations/warnings/services/organization-warnings.service.ts
index 5b466dfe41d..c6bb1bc231b 100644
--- a/apps/web/src/app/billing/organizations/warnings/services/organization-warnings.service.ts
+++ b/apps/web/src/app/billing/organizations/warnings/services/organization-warnings.service.ts
@@ -63,6 +63,7 @@ export class OrganizationWarningsService {
getFreeTrialWarning$ = (
organization: Organization,
+ includeOrganizationNameInMessaging = false,
): Observable =>
merge(
this.getWarning$(organization, (response) => response.freeTrial),
@@ -80,20 +81,30 @@ export class OrganizationWarningsService {
if (remainingTrialDays >= 2) {
return {
organization,
- message: this.i18nService.t("freeTrialEndPromptCount", remainingTrialDays),
+ message: includeOrganizationNameInMessaging
+ ? this.i18nService.t(
+ "freeTrialEndPromptMultipleDays",
+ organization.name,
+ remainingTrialDays,
+ )
+ : this.i18nService.t("freeTrialEndPromptCount", remainingTrialDays),
};
}
if (remainingTrialDays == 1) {
return {
organization,
- message: this.i18nService.t("freeTrialEndPromptTomorrowNoOrgName"),
+ message: includeOrganizationNameInMessaging
+ ? this.i18nService.t("freeTrialEndPromptTomorrow", organization.name)
+ : this.i18nService.t("freeTrialEndPromptTomorrowNoOrgName"),
};
}
return {
organization,
- message: this.i18nService.t("freeTrialEndingTodayWithoutOrgName"),
+ message: includeOrganizationNameInMessaging
+ ? this.i18nService.t("freeTrialEndPromptToday", organization.name)
+ : this.i18nService.t("freeTrialEndingTodayWithoutOrgName"),
};
}),
);
diff --git a/apps/web/src/app/billing/services/reseller-warning.service.ts b/apps/web/src/app/billing/services/reseller-warning.service.ts
deleted file mode 100644
index 2c59ebafe05..00000000000
--- a/apps/web/src/app/billing/services/reseller-warning.service.ts
+++ /dev/null
@@ -1,142 +0,0 @@
-import { Injectable } from "@angular/core";
-
-import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
-import { OrganizationBillingMetadataResponse } from "@bitwarden/common/billing/models/response/organization-billing-metadata.response";
-import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
-
-export interface ResellerWarning {
- type: "info" | "warning";
- message: string;
-}
-
-@Injectable({ providedIn: "root" })
-export class ResellerWarningService {
- private readonly RENEWAL_WARNING_DAYS = 14;
- private readonly GRACE_PERIOD_DAYS = 30;
-
- constructor(private i18nService: I18nService) {}
-
- getWarning(
- organization: Organization,
- organizationBillingMetadata: OrganizationBillingMetadataResponse,
- ): ResellerWarning | null {
- if (!organization.hasReseller) {
- return null; // If no reseller, return null immediately
- }
-
- // Check for past due warning first (highest priority)
- if (this.shouldShowPastDueWarning(organizationBillingMetadata)) {
- const gracePeriodEnd = this.getGracePeriodEndDate(organizationBillingMetadata.invoiceDueDate);
- if (!gracePeriodEnd) {
- return null;
- }
- return {
- type: "warning",
- message: this.i18nService.t(
- "resellerPastDueWarningMsg",
- organization.providerName,
- this.formatDate(gracePeriodEnd),
- ),
- } as ResellerWarning;
- }
-
- // Check for open invoice warning
- if (this.shouldShowInvoiceWarning(organizationBillingMetadata)) {
- const invoiceCreatedDate = organizationBillingMetadata.invoiceCreatedDate;
- const invoiceDueDate = organizationBillingMetadata.invoiceDueDate;
- if (!invoiceCreatedDate || !invoiceDueDate) {
- return null;
- }
- return {
- type: "info",
- message: this.i18nService.t(
- "resellerOpenInvoiceWarningMgs",
- organization.providerName,
- this.formatDate(organizationBillingMetadata.invoiceCreatedDate),
- this.formatDate(organizationBillingMetadata.invoiceDueDate),
- ),
- } as ResellerWarning;
- }
-
- // Check for renewal warning
- if (this.shouldShowRenewalWarning(organizationBillingMetadata)) {
- const subPeriodEndDate = organizationBillingMetadata.subPeriodEndDate;
- if (!subPeriodEndDate) {
- return null;
- }
-
- return {
- type: "info",
- message: this.i18nService.t(
- "resellerRenewalWarningMsg",
- organization.providerName,
- this.formatDate(organizationBillingMetadata.subPeriodEndDate),
- ),
- } as ResellerWarning;
- }
-
- return null;
- }
-
- private shouldShowRenewalWarning(
- organizationBillingMetadata: OrganizationBillingMetadataResponse,
- ): boolean {
- if (
- !organizationBillingMetadata.hasSubscription ||
- !organizationBillingMetadata.subPeriodEndDate
- ) {
- return false;
- }
- const renewalDate = new Date(organizationBillingMetadata.subPeriodEndDate);
- const daysUntilRenewal = Math.ceil(
- (renewalDate.getTime() - Date.now()) / (1000 * 60 * 60 * 24),
- );
- return daysUntilRenewal <= this.RENEWAL_WARNING_DAYS;
- }
-
- private shouldShowInvoiceWarning(
- organizationBillingMetadata: OrganizationBillingMetadataResponse,
- ): boolean {
- if (
- !organizationBillingMetadata.hasOpenInvoice ||
- !organizationBillingMetadata.invoiceDueDate
- ) {
- return false;
- }
- const invoiceDueDate = new Date(organizationBillingMetadata.invoiceDueDate);
- return invoiceDueDate > new Date();
- }
-
- private shouldShowPastDueWarning(
- organizationBillingMetadata: OrganizationBillingMetadataResponse,
- ): boolean {
- if (
- !organizationBillingMetadata.hasOpenInvoice ||
- !organizationBillingMetadata.invoiceDueDate
- ) {
- return false;
- }
- const invoiceDueDate = new Date(organizationBillingMetadata.invoiceDueDate);
- return invoiceDueDate <= new Date() && !organizationBillingMetadata.isSubscriptionUnpaid;
- }
-
- private getGracePeriodEndDate(dueDate: Date | null): Date | null {
- if (!dueDate) {
- return null;
- }
- const gracePeriodEnd = new Date(dueDate);
- gracePeriodEnd.setDate(gracePeriodEnd.getDate() + this.GRACE_PERIOD_DAYS);
- return gracePeriodEnd;
- }
-
- private formatDate(date: Date | null): string {
- if (!date) {
- return "N/A";
- }
- return new Date(date).toLocaleDateString("en-US", {
- month: "short",
- day: "2-digit",
- year: "numeric",
- });
- }
-}
diff --git a/apps/web/src/app/billing/services/trial-flow.service.ts b/apps/web/src/app/billing/services/trial-flow.service.ts
deleted file mode 100644
index 831cc129e60..00000000000
--- a/apps/web/src/app/billing/services/trial-flow.service.ts
+++ /dev/null
@@ -1,162 +0,0 @@
-// FIXME: Update this file to be type safe and remove this and next line
-// @ts-strict-ignore
-import { Injectable } from "@angular/core";
-import { Router } from "@angular/router";
-import { lastValueFrom } from "rxjs";
-
-import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
-import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
-import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/billing-api.service.abstraction";
-import { BillingSourceResponse } from "@bitwarden/common/billing/models/response/billing.response";
-import { OrganizationBillingMetadataResponse } from "@bitwarden/common/billing/models/response/organization-billing-metadata.response";
-import { OrganizationSubscriptionResponse } from "@bitwarden/common/billing/models/response/organization-subscription.response";
-import { PaymentSourceResponse } from "@bitwarden/common/billing/models/response/payment-source.response";
-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 { DialogService } from "@bitwarden/components";
-
-import {
- ChangePlanDialogResultType,
- openChangePlanDialog,
-} from "../organizations/change-plan-dialog.component";
-import { FreeTrial } from "../types/free-trial";
-
-@Injectable({ providedIn: "root" })
-export class TrialFlowService {
- constructor(
- private i18nService: I18nService,
- protected dialogService: DialogService,
- private router: Router,
- protected billingApiService: BillingApiServiceAbstraction,
- private organizationApiService: OrganizationApiServiceAbstraction,
- private configService: ConfigService,
- ) {}
- checkForOrgsWithUpcomingPaymentIssues(
- organization: Organization,
- organizationSubscription: OrganizationSubscriptionResponse,
- paymentSource: BillingSourceResponse | PaymentSourceResponse,
- ): FreeTrial {
- const trialEndDate = organizationSubscription?.subscription?.trialEndDate;
- const displayBanner =
- !paymentSource &&
- organization?.isOwner &&
- organizationSubscription?.subscription?.status === "trialing";
- const trialRemainingDays = trialEndDate ? this.calculateTrialRemainingDays(trialEndDate) : 0;
- const freeTrialMessage = this.getFreeTrialMessage(trialRemainingDays);
-
- return {
- remainingDays: trialRemainingDays,
- message: freeTrialMessage,
- shownBanner: displayBanner,
- organizationId: organization.id,
- organizationName: organization.name,
- };
- }
-
- calculateTrialRemainingDays(trialEndDate: string): number | undefined {
- const today = new Date();
- const trialEnd = new Date(trialEndDate);
- const timeDifference = trialEnd.getTime() - today.getTime();
-
- return Math.ceil(timeDifference / (1000 * 60 * 60 * 24));
- }
-
- getFreeTrialMessage(trialRemainingDays: number): string {
- if (trialRemainingDays >= 2) {
- return this.i18nService.t("freeTrialEndPromptCount", trialRemainingDays);
- } else if (trialRemainingDays === 1) {
- return this.i18nService.t("freeTrialEndPromptTomorrowNoOrgName");
- } else {
- return this.i18nService.t("freeTrialEndingTodayWithoutOrgName");
- }
- }
-
- async handleUnpaidSubscriptionDialog(
- org: Organization,
- organizationBillingMetadata: OrganizationBillingMetadataResponse,
- ): Promise {
- if (
- organizationBillingMetadata.isSubscriptionUnpaid ||
- organizationBillingMetadata.isSubscriptionCanceled
- ) {
- const confirmed = await this.promptForPaymentNavigation(
- org,
- organizationBillingMetadata.isSubscriptionCanceled,
- organizationBillingMetadata.isSubscriptionUnpaid,
- );
- if (confirmed) {
- await this.navigateToPaymentMethod(org?.id);
- }
- }
- }
-
- private async promptForPaymentNavigation(
- org: Organization,
- isCanceled: boolean,
- isUnpaid: boolean,
- ): Promise {
- if (!org?.isOwner && !org.providerId) {
- await this.dialogService.openSimpleDialog({
- title: this.i18nService.t("suspendedOrganizationTitle", org?.name),
- content: { key: "suspendedUserOrgMessage" },
- type: "danger",
- acceptButtonText: this.i18nService.t("close"),
- cancelButtonText: null,
- });
- return false;
- }
-
- if (org.providerId) {
- await this.dialogService.openSimpleDialog({
- title: this.i18nService.t("suspendedOrganizationTitle", org.name),
- content: { key: "suspendedManagedOrgMessage", placeholders: [org.providerName] },
- type: "danger",
- acceptButtonText: this.i18nService.t("close"),
- cancelButtonText: null,
- });
- return false;
- }
-
- if (org.isOwner && isUnpaid) {
- return await this.dialogService.openSimpleDialog({
- title: this.i18nService.t("suspendedOrganizationTitle", org.name),
- content: { key: "suspendedOwnerOrgMessage" },
- type: "danger",
- acceptButtonText: this.i18nService.t("continue"),
- cancelButtonText: this.i18nService.t("close"),
- });
- }
-
- if (org.isOwner && isCanceled) {
- await this.changePlan(org);
- }
- }
-
- private async navigateToPaymentMethod(orgId: string) {
- const managePaymentDetailsOutsideCheckout = await this.configService.getFeatureFlag(
- FeatureFlag.PM21881_ManagePaymentDetailsOutsideCheckout,
- );
- const route = managePaymentDetailsOutsideCheckout ? "payment-details" : "payment-method";
- await this.router.navigate(["organizations", `${orgId}`, "billing", route], {
- state: { launchPaymentModalAutomatically: true },
- queryParams: { launchPaymentModalAutomatically: true },
- });
- }
-
- private async changePlan(org: Organization) {
- const subscription = await this.organizationApiService.getSubscription(org.id);
- const reference = openChangePlanDialog(this.dialogService, {
- data: {
- organizationId: org.id,
- subscription: subscription,
- productTierType: org.productTierType,
- },
- });
-
- const result = await lastValueFrom(reference.closed);
- if (result === ChangePlanDialogResultType.Closed) {
- return;
- }
- }
-}
diff --git a/apps/web/src/app/billing/shared/payment-method.component.html b/apps/web/src/app/billing/shared/payment-method.component.html
index 6d3b1adff9c..81ed7e5a631 100644
--- a/apps/web/src/app/billing/shared/payment-method.component.html
+++ b/apps/web/src/app/billing/shared/payment-method.component.html
@@ -1,22 +1,3 @@
-
- {{ freeTrialData?.message }}
-
- {{ "clickHereToAddPaymentMethod" | i18n }}
-
-
-