mirror of
https://github.com/bitwarden/browser
synced 2025-12-18 17:23:37 +00:00
[PM-16937] Remove Billing Circular Dependency (#13085)
* Remove circular dependency between billing services and components * Removed `logService` from `billing-api.service.ts` * Resolved failed test * Removed @bitwarden/ui-common * Added optional `title` parameter to `BillingNotificationService` functions * Removed @bitwarden/platform from libs/common/tsconfig.json * Update apps/web/src/app/billing/services/billing-notification.service.spec.ts Co-authored-by: Andreas Coroiu <acoroiu@bitwarden.com> * Update apps/web/src/app/billing/services/billing-notification.service.spec.ts Co-authored-by: Andreas Coroiu <acoroiu@bitwarden.com> * Resolved build errors * Resolved issue where free trial banner wouldn't display if missing a payment method --------- Co-authored-by: Andreas Coroiu <acoroiu@bitwarden.com>
This commit is contained in:
@@ -8,6 +8,7 @@ import { map } from "rxjs";
|
||||
|
||||
import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions";
|
||||
import { InvoiceResponse } from "@bitwarden/common/billing/models/response/invoices.response";
|
||||
import { BillingNotificationService } from "@bitwarden/web-vault/app/billing/services/billing-notification.service";
|
||||
|
||||
@Component({
|
||||
templateUrl: "./provider-billing-history.component.html",
|
||||
@@ -19,6 +20,7 @@ export class ProviderBillingHistoryComponent {
|
||||
private activatedRoute: ActivatedRoute,
|
||||
private billingApiService: BillingApiServiceAbstraction,
|
||||
private datePipe: DatePipe,
|
||||
private billingNotificationService: BillingNotificationService,
|
||||
) {
|
||||
this.activatedRoute.params
|
||||
.pipe(
|
||||
@@ -30,13 +32,27 @@ export class ProviderBillingHistoryComponent {
|
||||
.subscribe();
|
||||
}
|
||||
|
||||
getClientInvoiceReport = (invoiceId: string) =>
|
||||
this.billingApiService.getProviderClientInvoiceReport(this.providerId, invoiceId);
|
||||
getClientInvoiceReport = async (invoiceId: string) => {
|
||||
try {
|
||||
return await this.billingApiService.getProviderClientInvoiceReport(
|
||||
this.providerId,
|
||||
invoiceId,
|
||||
);
|
||||
} catch (error) {
|
||||
this.billingNotificationService.handleError(error);
|
||||
}
|
||||
};
|
||||
|
||||
getClientInvoiceReportName = (invoice: InvoiceResponse) => {
|
||||
const date = this.datePipe.transform(invoice.date, "yyyyMMdd");
|
||||
return `bitwarden_provider-billing-history_${date}_${invoice.number}`;
|
||||
};
|
||||
|
||||
getInvoices = async () => await this.billingApiService.getProviderInvoices(this.providerId);
|
||||
getInvoices = async () => {
|
||||
try {
|
||||
return await this.billingApiService.getProviderInvoices(this.providerId);
|
||||
} catch (error) {
|
||||
this.billingNotificationService.handleError(error);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -11,7 +11,8 @@ import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstract
|
||||
import { UpdateClientOrganizationRequest } from "@bitwarden/common/billing/models/request/update-client-organization.request";
|
||||
import { ProviderPlanResponse } from "@bitwarden/common/billing/models/response/provider-subscription-response";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { DialogService, ToastService } from "@bitwarden/components";
|
||||
import { DialogService } from "@bitwarden/components";
|
||||
import { BillingNotificationService } from "@bitwarden/web-vault/app/billing/services/billing-notification.service";
|
||||
|
||||
type ManageClientSubscriptionDialogParams = {
|
||||
organization: ProviderOrganizationOrganizationDetailsResponse;
|
||||
@@ -56,30 +57,34 @@ export class ManageClientSubscriptionDialogComponent implements OnInit {
|
||||
@Inject(DIALOG_DATA) protected dialogParams: ManageClientSubscriptionDialogParams,
|
||||
private dialogRef: DialogRef<ManageClientSubscriptionDialogResultType>,
|
||||
private i18nService: I18nService,
|
||||
private toastService: ToastService,
|
||||
private billingNotificationService: BillingNotificationService,
|
||||
) {}
|
||||
|
||||
async ngOnInit(): Promise<void> {
|
||||
const response = await this.billingApiService.getProviderSubscription(
|
||||
this.dialogParams.provider.id,
|
||||
);
|
||||
try {
|
||||
const response = await this.billingApiService.getProviderSubscription(
|
||||
this.dialogParams.provider.id,
|
||||
);
|
||||
|
||||
this.providerPlan = response.plans.find(
|
||||
(plan) => plan.planName === this.dialogParams.organization.plan,
|
||||
);
|
||||
this.providerPlan = response.plans.find(
|
||||
(plan) => plan.planName === this.dialogParams.organization.plan,
|
||||
);
|
||||
|
||||
this.assignedSeats = this.providerPlan.assignedSeats;
|
||||
this.openSeats = this.providerPlan.seatMinimum - this.providerPlan.assignedSeats;
|
||||
this.purchasedSeats = this.providerPlan.purchasedSeats;
|
||||
this.seatMinimum = this.providerPlan.seatMinimum;
|
||||
this.assignedSeats = this.providerPlan.assignedSeats;
|
||||
this.openSeats = this.providerPlan.seatMinimum - this.providerPlan.assignedSeats;
|
||||
this.purchasedSeats = this.providerPlan.purchasedSeats;
|
||||
this.seatMinimum = this.providerPlan.seatMinimum;
|
||||
|
||||
this.formGroup.controls.assignedSeats.addValidators(
|
||||
this.isServiceUserWithPurchasedSeats
|
||||
? this.createPurchasedSeatsValidator()
|
||||
: this.createUnassignedSeatsValidator(),
|
||||
);
|
||||
|
||||
this.loading = false;
|
||||
this.formGroup.controls.assignedSeats.addValidators(
|
||||
this.isServiceUserWithPurchasedSeats
|
||||
? this.createPurchasedSeatsValidator()
|
||||
: this.createUnassignedSeatsValidator(),
|
||||
);
|
||||
} catch (error) {
|
||||
this.billingNotificationService.handleError(error);
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
submit = async () => {
|
||||
@@ -91,24 +96,25 @@ export class ManageClientSubscriptionDialogComponent implements OnInit {
|
||||
return;
|
||||
}
|
||||
|
||||
const request = new UpdateClientOrganizationRequest();
|
||||
request.assignedSeats = this.formGroup.value.assignedSeats;
|
||||
request.name = this.dialogParams.organization.organizationName;
|
||||
try {
|
||||
const request = new UpdateClientOrganizationRequest();
|
||||
request.assignedSeats = this.formGroup.value.assignedSeats;
|
||||
request.name = this.dialogParams.organization.organizationName;
|
||||
|
||||
await this.billingApiService.updateProviderClientOrganization(
|
||||
this.dialogParams.provider.id,
|
||||
this.dialogParams.organization.id,
|
||||
request,
|
||||
);
|
||||
await this.billingApiService.updateProviderClientOrganization(
|
||||
this.dialogParams.provider.id,
|
||||
this.dialogParams.organization.id,
|
||||
request,
|
||||
);
|
||||
|
||||
this.toastService.showToast({
|
||||
variant: "success",
|
||||
title: null,
|
||||
message: this.i18nService.t("subscriptionUpdated"),
|
||||
});
|
||||
this.billingNotificationService.showSuccess(this.i18nService.t("subscriptionUpdated"));
|
||||
|
||||
this.loading = false;
|
||||
this.dialogRef.close(this.ResultType.Submitted);
|
||||
this.dialogRef.close(this.ResultType.Submitted);
|
||||
} catch (error) {
|
||||
this.billingNotificationService.handleError(error);
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
};
|
||||
|
||||
createPurchasedSeatsValidator =
|
||||
|
||||
@@ -23,6 +23,7 @@ import {
|
||||
ToastService,
|
||||
} from "@bitwarden/components";
|
||||
import { SharedOrganizationModule } from "@bitwarden/web-vault/app/admin-console/organizations/shared";
|
||||
import { BillingNotificationService } from "@bitwarden/web-vault/app/billing/services/billing-notification.service";
|
||||
import { HeaderModule } from "@bitwarden/web-vault/app/layouts/header/header.module";
|
||||
|
||||
import { WebProviderService } from "../../../admin-console/providers/services/web-provider.service";
|
||||
@@ -83,6 +84,7 @@ export class ManageClientsComponent {
|
||||
private validationService: ValidationService,
|
||||
private webProviderService: WebProviderService,
|
||||
private configService: ConfigService,
|
||||
private billingNotificationService: BillingNotificationService,
|
||||
) {
|
||||
this.activatedRoute.queryParams.pipe(first(), takeUntilDestroyed()).subscribe((queryParams) => {
|
||||
this.searchControl.setValue(queryParams.search);
|
||||
@@ -120,13 +122,17 @@ export class ManageClientsComponent {
|
||||
}
|
||||
|
||||
async load() {
|
||||
this.provider = await firstValueFrom(this.providerService.get$(this.providerId));
|
||||
this.isProviderAdmin = this.provider?.type === ProviderUserType.ProviderAdmin;
|
||||
this.dataSource.data = (
|
||||
await this.billingApiService.getProviderClientOrganizations(this.providerId)
|
||||
).data;
|
||||
this.plans = (await this.billingApiService.getPlans()).data;
|
||||
this.loading = false;
|
||||
try {
|
||||
this.provider = await firstValueFrom(this.providerService.get$(this.providerId));
|
||||
this.isProviderAdmin = this.provider?.type === ProviderUserType.ProviderAdmin;
|
||||
this.dataSource.data = (
|
||||
await this.billingApiService.getProviderClientOrganizations(this.providerId)
|
||||
).data;
|
||||
this.plans = (await this.billingApiService.getPlans()).data;
|
||||
this.loading = false;
|
||||
} catch (error) {
|
||||
this.billingNotificationService.handleError(error);
|
||||
}
|
||||
}
|
||||
|
||||
addExistingOrganization = async () => {
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
ProviderSubscriptionResponse,
|
||||
} from "@bitwarden/common/billing/models/response/provider-subscription-response";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { ToastService } from "@bitwarden/components";
|
||||
import { BillingNotificationService } from "@bitwarden/web-vault/app/billing/services/billing-notification.service";
|
||||
|
||||
@Component({
|
||||
selector: "app-provider-subscription",
|
||||
@@ -33,7 +33,7 @@ export class ProviderSubscriptionComponent implements OnInit, OnDestroy {
|
||||
private billingApiService: BillingApiServiceAbstraction,
|
||||
private i18nService: I18nService,
|
||||
private route: ActivatedRoute,
|
||||
private toastService: ToastService,
|
||||
private billingNotificationService: BillingNotificationService,
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
@@ -54,20 +54,26 @@ export class ProviderSubscriptionComponent implements OnInit, OnDestroy {
|
||||
return;
|
||||
}
|
||||
this.loading = true;
|
||||
this.subscription = await this.billingApiService.getProviderSubscription(this.providerId);
|
||||
this.totalCost =
|
||||
((100 - this.subscription.discountPercentage) / 100) * this.sumCost(this.subscription.plans);
|
||||
this.loading = false;
|
||||
try {
|
||||
this.subscription = await this.billingApiService.getProviderSubscription(this.providerId);
|
||||
this.totalCost =
|
||||
((100 - this.subscription.discountPercentage) / 100) *
|
||||
this.sumCost(this.subscription.plans);
|
||||
} catch (error) {
|
||||
this.billingNotificationService.handleError(error);
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
protected updateTaxInformation = async (taxInformation: TaxInformation) => {
|
||||
const request = ExpandedTaxInfoUpdateRequest.From(taxInformation);
|
||||
await this.billingApiService.updateProviderTaxInformation(this.providerId, request);
|
||||
this.toastService.showToast({
|
||||
variant: "success",
|
||||
title: null,
|
||||
message: this.i18nService.t("updatedTaxInformation"),
|
||||
});
|
||||
try {
|
||||
const request = ExpandedTaxInfoUpdateRequest.From(taxInformation);
|
||||
await this.billingApiService.updateProviderTaxInformation(this.providerId, request);
|
||||
this.billingNotificationService.showSuccess(this.i18nService.t("updatedTaxInformation"));
|
||||
} catch (error) {
|
||||
this.billingNotificationService.handleError(error);
|
||||
}
|
||||
};
|
||||
|
||||
protected getFormattedCost(
|
||||
|
||||
@@ -16,6 +16,8 @@ import {
|
||||
firstValueFrom,
|
||||
of,
|
||||
filter,
|
||||
catchError,
|
||||
from,
|
||||
} from "rxjs";
|
||||
|
||||
import { I18nPipe } from "@bitwarden/angular/platform/pipes/i18n.pipe";
|
||||
@@ -32,6 +34,7 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { DialogService } from "@bitwarden/components";
|
||||
import { BillingNotificationService } from "@bitwarden/web-vault/app/billing/services/billing-notification.service";
|
||||
import { TrialFlowService } from "@bitwarden/web-vault/app/billing/services/trial-flow.service";
|
||||
import { FreeTrial } from "@bitwarden/web-vault/app/billing/types/free-trial";
|
||||
|
||||
@@ -126,6 +129,7 @@ export class OverviewComponent implements OnInit, OnDestroy {
|
||||
private organizationApiService: OrganizationApiServiceAbstraction,
|
||||
private trialFlowService: TrialFlowService,
|
||||
private organizationBillingService: OrganizationBillingServiceAbstraction,
|
||||
private billingNotificationService: BillingNotificationService,
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
@@ -161,12 +165,18 @@ export class OverviewComponent implements OnInit, OnDestroy {
|
||||
combineLatest([
|
||||
of(org),
|
||||
this.organizationApiService.getSubscription(org.id),
|
||||
this.organizationBillingService.getPaymentSource(org.id),
|
||||
from(this.organizationBillingService.getPaymentSource(org.id)).pipe(
|
||||
catchError((error: unknown) => {
|
||||
this.billingNotificationService.handleError(error);
|
||||
return of(null);
|
||||
}),
|
||||
),
|
||||
]),
|
||||
),
|
||||
map(([org, sub, paymentSource]) => {
|
||||
return this.trialFlowService.checkForOrgsWithUpcomingPaymentIssues(org, sub, paymentSource);
|
||||
}),
|
||||
filter((result) => result !== null),
|
||||
takeUntil(this.destroy$),
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user