1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-06 00:13:28 +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:
Conner Turnbull
2025-03-11 13:43:19 -04:00
committed by GitHub
parent 18ad710909
commit 00e822fb13
15 changed files with 327 additions and 168 deletions

View File

@@ -24,6 +24,7 @@ import {
switchMap,
takeUntil,
tap,
catchError,
} from "rxjs/operators";
import {
@@ -76,6 +77,7 @@ import {
PasswordRepromptService,
} from "@bitwarden/vault";
import { BillingNotificationService } from "../../../billing/services/billing-notification.service";
import {
ResellerWarning,
ResellerWarningService,
@@ -256,6 +258,7 @@ export class VaultComponent implements OnInit, OnDestroy {
private organizationBillingService: OrganizationBillingServiceAbstraction,
private resellerWarningService: ResellerWarningService,
private accountService: AccountService,
private billingNotificationService: BillingNotificationService,
) {}
async ngOnInit() {
@@ -636,12 +639,18 @@ export class VaultComponent 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);
}),
map(([org, sub, paymentSource]) =>
this.trialFlowService.checkForOrgsWithUpcomingPaymentIssues(org, sub, paymentSource),
),
filter((result) => result !== null),
);
this.resellerWarning$ = organization$.pipe(

View File

@@ -58,6 +58,7 @@ import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.serv
import { DialogService, ToastService } from "@bitwarden/components";
import { KeyService } from "@bitwarden/key-management";
import { BillingNotificationService } from "../services/billing-notification.service";
import { BillingSharedModule } from "../shared/billing-shared.module";
import { PaymentComponent } from "../shared/payment/payment.component";
@@ -208,6 +209,7 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
private taxService: TaxServiceAbstraction,
private accountService: AccountService,
private organizationBillingService: OrganizationBillingService,
private billingNotificationService: BillingNotificationService,
) {}
async ngOnInit(): Promise<void> {
@@ -228,10 +230,14 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
.organizations$(userId)
.pipe(getOrganizationById(this.organizationId)),
);
const { accountCredit, paymentSource } =
await this.billingApiService.getOrganizationPaymentMethod(this.organizationId);
this.accountCredit = accountCredit;
this.paymentSource = paymentSource;
try {
const { accountCredit, paymentSource } =
await this.billingApiService.getOrganizationPaymentMethod(this.organizationId);
this.accountCredit = accountCredit;
this.paymentSource = paymentSource;
} catch (error) {
this.billingNotificationService.handleError(error);
}
}
if (!this.selfHosted) {

View File

@@ -23,6 +23,7 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl
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,
@@ -66,6 +67,7 @@ export class OrganizationPaymentMethodComponent implements OnDestroy {
private organizationService: OrganizationService,
private accountService: AccountService,
protected syncService: SyncService,
private billingNotificationService: BillingNotificationService,
) {
this.activatedRoute.params
.pipe(
@@ -115,47 +117,52 @@ export class OrganizationPaymentMethodComponent implements OnDestroy {
protected load = async (): Promise<void> => {
this.loading = true;
const { accountCredit, paymentSource, subscriptionStatus } =
await this.billingApiService.getOrganizationPaymentMethod(this.organizationId);
this.accountCredit = accountCredit;
this.paymentSource = paymentSource;
this.subscriptionStatus = subscriptionStatus;
try {
const { accountCredit, paymentSource, subscriptionStatus } =
await this.billingApiService.getOrganizationPaymentMethod(this.organizationId);
this.accountCredit = accountCredit;
this.paymentSource = paymentSource;
this.subscriptionStatus = subscriptionStatus;
if (this.organizationId) {
const organizationSubscriptionPromise = this.organizationApiService.getSubscription(
this.organizationId,
);
const userId = await firstValueFrom(
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
);
const organizationPromise = await firstValueFrom(
this.organizationService
.organizations$(userId)
.pipe(getOrganizationById(this.organizationId)),
);
if (this.organizationId) {
const organizationSubscriptionPromise = this.organizationApiService.getSubscription(
this.organizationId,
);
const userId = await firstValueFrom(
this.accountService.activeAccount$.pipe(map((a) => a?.id)),
);
const organizationPromise = await firstValueFrom(
this.organizationService
.organizations$(userId)
.pipe(getOrganizationById(this.organizationId)),
);
[this.organizationSubscriptionResponse, this.organization] = await Promise.all([
organizationSubscriptionPromise,
organizationPromise,
]);
this.freeTrialData = this.trialFlowService.checkForOrgsWithUpcomingPaymentIssues(
this.organization,
this.organizationSubscriptionResponse,
paymentSource,
);
[this.organizationSubscriptionResponse, this.organization] = await Promise.all([
organizationSubscriptionPromise,
organizationPromise,
]);
this.freeTrialData = this.trialFlowService.checkForOrgsWithUpcomingPaymentIssues(
this.organization,
this.organizationSubscriptionResponse,
paymentSource,
);
}
this.isUnpaid = this.subscriptionStatus === "unpaid" ?? false;
// If the flag `launchPaymentModalAutomatically` is set to true,
// we schedule a timeout (delay of 800ms) to automatically launch the payment modal.
// This delay ensures that any prior UI/rendering operations complete before triggering the modal.
if (this.launchPaymentModalAutomatically) {
window.setTimeout(async () => {
await this.changePayment();
this.launchPaymentModalAutomatically = false;
this.location.replaceState(this.location.path(), "", {});
}, 800);
}
} catch (error) {
this.billingNotificationService.handleError(error);
} finally {
this.loading = false;
}
this.isUnpaid = this.subscriptionStatus === "unpaid" ?? false;
// If the flag `launchPaymentModalAutomatically` is set to true,
// we schedule a timeout (delay of 800ms) to automatically launch the payment modal.
// This delay ensures that any prior UI/rendering operations complete before triggering the modal.
if (this.launchPaymentModalAutomatically) {
window.setTimeout(async () => {
await this.changePayment();
this.launchPaymentModalAutomatically = false;
this.location.replaceState(this.location.path(), "", {});
}, 800);
}
this.loading = false;
};
protected updatePaymentMethod = async (): Promise<void> => {

View File

@@ -0,0 +1,76 @@
import { mock, MockProxy } from "jest-mock-extended";
import { ErrorResponse } from "@bitwarden/common/models/response/error.response";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { ToastService } from "@bitwarden/components";
import { BillingNotificationService } from "./billing-notification.service";
describe("BillingNotificationService", () => {
let service: BillingNotificationService;
let logService: MockProxy<LogService>;
let toastService: MockProxy<ToastService>;
beforeEach(() => {
logService = mock<LogService>();
toastService = mock<ToastService>();
service = new BillingNotificationService(logService, toastService);
});
describe("handleError", () => {
it("should log error and show toast for ErrorResponse", () => {
const error = new ErrorResponse(["test error"], 400);
expect(() => service.handleError(error)).toThrow();
expect(logService.error).toHaveBeenCalledWith(error);
expect(toastService.showToast).toHaveBeenCalledWith({
variant: "error",
title: "",
message: error.getSingleMessage(),
});
});
it("shows error toast with the provided error", () => {
const error = new ErrorResponse(["test error"], 400);
expect(() => service.handleError(error, "Test Title")).toThrow();
expect(toastService.showToast).toHaveBeenCalledWith({
variant: "error",
title: "Test Title",
message: error.getSingleMessage(),
});
});
it("should only log error for non-ErrorResponse", () => {
const error = new Error("test error");
expect(() => service.handleError(error)).toThrow();
expect(logService.error).toHaveBeenCalledWith(error);
expect(toastService.showToast).not.toHaveBeenCalled();
});
});
describe("showSuccess", () => {
it("shows success toast with default title when provided title is empty", () => {
const message = "test message";
service.showSuccess(message);
expect(toastService.showToast).toHaveBeenCalledWith({
variant: "success",
title: "",
message,
});
});
it("should show success toast with custom title", () => {
const message = "test message";
service.showSuccess(message, "Success Title");
expect(toastService.showToast).toHaveBeenCalledWith({
variant: "success",
title: "Success Title",
message,
});
});
});
});

View File

@@ -0,0 +1,35 @@
import { Injectable } from "@angular/core";
import { ErrorResponse } from "@bitwarden/common/models/response/error.response";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { ToastService } from "@bitwarden/components";
@Injectable({
providedIn: "root",
})
export class BillingNotificationService {
constructor(
private logService: LogService,
private toastService: ToastService,
) {}
handleError(error: unknown, title: string = "") {
this.logService.error(error);
if (error instanceof ErrorResponse) {
this.toastService.showToast({
variant: "error",
title: title,
message: error.getSingleMessage(),
});
}
throw error;
}
showSuccess(message: string, title: string = "") {
this.toastService.showToast({
variant: "success",
title: title,
message: message,
});
}
}

View File

@@ -1,4 +1,8 @@
import { NgModule } from "@angular/core";
@NgModule({})
import { BillingNotificationService } from "./billing-notification.service";
@NgModule({
providers: [BillingNotificationService],
})
export class BillingServicesModule {}

View File

@@ -24,6 +24,7 @@ import {
take,
takeUntil,
tap,
catchError,
} from "rxjs/operators";
import {
@@ -80,6 +81,7 @@ import {
CollectionDialogTabType,
openCollectionDialog,
} from "../../admin-console/organizations/shared/components/collection-dialog";
import { BillingNotificationService } from "../../billing/services/billing-notification.service";
import { TrialFlowService } from "../../billing/services/trial-flow.service";
import { FreeTrial } from "../../billing/types/free-trial";
import { SharedModule } from "../../shared/shared.module";
@@ -213,20 +215,25 @@ export class VaultComponent implements OnInit, OnDestroy {
ownerOrgs.map((org) =>
combineLatest([
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);
}),
),
]).pipe(
map(([subscription, paymentSource]) => {
return this.trialFlowService.checkForOrgsWithUpcomingPaymentIssues(
map(([subscription, paymentSource]) =>
this.trialFlowService.checkForOrgsWithUpcomingPaymentIssues(
org,
subscription,
paymentSource,
);
}),
),
),
),
),
);
}),
map((results) => results.filter((result) => result.shownBanner)),
map((results) => results.filter((result) => result !== null && result.shownBanner)),
shareReplay({ refCount: false, bufferSize: 1 }),
);
@@ -262,6 +269,7 @@ export class VaultComponent implements OnInit, OnDestroy {
protected billingApiService: BillingApiServiceAbstraction,
private trialFlowService: TrialFlowService,
private organizationBillingService: OrganizationBillingServiceAbstraction,
private billingNotificationService: BillingNotificationService,
) {}
async ngOnInit() {

View File

@@ -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);
}
};
}

View File

@@ -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 =

View File

@@ -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 () => {

View File

@@ -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(

View File

@@ -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$),
);

View File

@@ -1284,7 +1284,7 @@ const safeProviders: SafeProvider[] = [
safeProvider({
provide: BillingApiServiceAbstraction,
useClass: BillingApiService,
deps: [ApiServiceAbstraction, LogService, ToastService],
deps: [ApiServiceAbstraction],
}),
safeProvider({
provide: TaxServiceAbstraction,

View File

@@ -1,13 +1,10 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { ToastService } from "@bitwarden/components";
import { ApiService } from "../../abstractions/api.service";
import { OrganizationCreateRequest } from "../../admin-console/models/request/organization-create.request";
import { ProviderOrganizationOrganizationDetailsResponse } from "../../admin-console/models/response/provider/provider-organization.response";
import { ErrorResponse } from "../../models/response/error.response";
import { ListResponse } from "../../models/response/list.response";
import { LogService } from "../../platform/abstractions/log.service";
import { BillingApiServiceAbstraction } from "../abstractions";
import { PaymentMethodType } from "../enums";
import { CreateClientOrganizationRequest } from "../models/request/create-client-organization.request";
@@ -23,11 +20,7 @@ import { PlanResponse } from "../models/response/plan.response";
import { ProviderSubscriptionResponse } from "../models/response/provider-subscription-response";
export class BillingApiService implements BillingApiServiceAbstraction {
constructor(
private apiService: ApiService,
private logService: LogService,
private toastService: ToastService,
) {}
constructor(private apiService: ApiService) {}
cancelOrganizationSubscription(
organizationId: string,
@@ -89,14 +82,12 @@ export class BillingApiService implements BillingApiServiceAbstraction {
}
async getOrganizationPaymentMethod(organizationId: string): Promise<PaymentMethodResponse> {
const response = await this.execute(() =>
this.apiService.send(
"GET",
"/organizations/" + organizationId + "/billing/payment-method",
null,
true,
true,
),
const response = await this.apiService.send(
"GET",
"/organizations/" + organizationId + "/billing/payment-method",
null,
true,
true,
);
return new PaymentMethodResponse(response);
}
@@ -120,34 +111,34 @@ export class BillingApiService implements BillingApiServiceAbstraction {
async getProviderClientOrganizations(
providerId: string,
): Promise<ListResponse<ProviderOrganizationOrganizationDetailsResponse>> {
const response = await this.execute(() =>
this.apiService.send("GET", "/providers/" + providerId + "/organizations", null, true, true),
const response = await this.apiService.send(
"GET",
"/providers/" + providerId + "/organizations",
null,
true,
true,
);
return new ListResponse(response, ProviderOrganizationOrganizationDetailsResponse);
}
async getProviderInvoices(providerId: string): Promise<InvoicesResponse> {
const response = await this.execute(() =>
this.apiService.send(
"GET",
"/providers/" + providerId + "/billing/invoices",
null,
true,
true,
),
const response = await this.apiService.send(
"GET",
"/providers/" + providerId + "/billing/invoices",
null,
true,
true,
);
return new InvoicesResponse(response);
}
async getProviderSubscription(providerId: string): Promise<ProviderSubscriptionResponse> {
const response = await this.execute(() =>
this.apiService.send(
"GET",
"/providers/" + providerId + "/billing/subscription",
null,
true,
true,
),
const response = await this.apiService.send(
"GET",
"/providers/" + providerId + "/billing/subscription",
null,
true,
true,
);
return new ProviderSubscriptionResponse(response);
}
@@ -227,20 +218,4 @@ export class BillingApiService implements BillingApiServiceAbstraction {
false,
);
}
private async execute(request: () => Promise<any>): Promise<any> {
try {
return await request();
} catch (error) {
this.logService.error(error);
if (error instanceof ErrorResponse) {
this.toastService.showToast({
variant: "error",
title: null,
message: error.getSingleMessage(),
});
}
throw error;
}
}
}

View File

@@ -6,13 +6,8 @@
"@bitwarden/auth/common": ["../auth/src/common"],
// TODO: Remove once circular dependencies in admin-console, auth and key-management are resolved
"@bitwarden/common/*": ["../common/src/*"],
// TODO: Remove once billing stops depending on components
"@bitwarden/components": ["../components/src"],
"@bitwarden/key-management": ["../key-management/src"],
"@bitwarden/vault-export-core": ["../tools/export/vault-export/vault-export-core/src"],
"@bitwarden/platform": ["../platform/src"],
// TODO: Remove once billing stops depending on components
"@bitwarden/ui-common": ["../ui/common/src"]
"@bitwarden/vault-export-core": ["../tools/export/vault-export/vault-export-core/src"]
}
},
"include": ["src", "spec", "./custom-matchers.d.ts", "../key-management/src/index.ts"],