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 270ba54f70d..01c451fe776 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 @@ -3,6 +3,7 @@ import { Location } from "@angular/common"; import { Component, OnDestroy } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; +import { FormGroup } from "@angular/forms"; import { ActivatedRoute, Router } from "@angular/router"; import { from, lastValueFrom, switchMap } from "rxjs"; @@ -17,9 +18,10 @@ import { PaymentSourceResponse } from "@bitwarden/common/billing/models/response import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { SyncService } from "@bitwarden/common/platform/sync"; -import { DialogService, ToastService } from "@bitwarden/components"; +import { DialogService } from "@bitwarden/components"; import { FreeTrial } from "../../../core/types/free-trial"; +import { BillingNotificationService } from "../../services/billing-notification.service"; import { TrialFlowService } from "../../services/trial-flow.service"; import { AddCreditDialogResult, @@ -42,6 +44,7 @@ export class OrganizationPaymentMethodComponent implements OnDestroy { protected freeTrialData: FreeTrial; organization: Organization; organizationSubscriptionResponse: OrganizationSubscriptionResponse; + protected verifyBankForm: FormGroup; loading = true; @@ -56,11 +59,11 @@ export class OrganizationPaymentMethodComponent implements OnDestroy { private i18nService: I18nService, private platformUtilsService: PlatformUtilsService, private router: Router, - private toastService: ToastService, private location: Location, private trialFlowService: TrialFlowService, private organizationService: OrganizationService, protected syncService: SyncService, + private notificationService: BillingNotificationService, ) { this.activatedRoute.params .pipe( @@ -182,12 +185,12 @@ export class OrganizationPaymentMethodComponent implements OnDestroy { }; protected verifyBankAccount = async (request: VerifyBankAccountRequest): Promise => { - await this.billingApiService.verifyOrganizationBankAccount(this.organizationId, request); - this.toastService.showToast({ - variant: "success", - title: null, - message: this.i18nService.t("verifiedBankAccount"), - }); + try { + await this.billingApiService.verifyOrganizationBankAccount(this.organizationId, request); + this.notificationService.showSuccess("verifiedBankAccount"); + } catch (error) { + this.notificationService.handleError(error); + } }; protected get accountCreditHeaderText(): string { @@ -221,4 +224,19 @@ export class OrganizationPaymentMethodComponent implements OnDestroy { const key = this.paymentSource == null ? "addPaymentMethod" : "changePaymentMethod"; return this.i18nService.t(key); } + + verifyBank = async () => { + if (this.loading || !this.organization) { + return; + } + + try { + const descriptorCode = `${this.verifyBankForm.value.amount1}${this.verifyBankForm.value.amount2}`; + const request = new VerifyBankAccountRequest(descriptorCode); + await this.verifyBankAccount(request); + await this.load(); + } catch (error) { + this.notificationService.handleError(error); + } + }; } diff --git a/apps/web/src/app/billing/services/billing-notification.service.ts b/apps/web/src/app/billing/services/billing-notification.service.ts new file mode 100644 index 00000000000..6af361150a6 --- /dev/null +++ b/apps/web/src/app/billing/services/billing-notification.service.ts @@ -0,0 +1,40 @@ +import { Injectable } from "@angular/core"; + +import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { ToastService } from "@bitwarden/components"; + +@Injectable({ + providedIn: "root", +}) +export class BillingNotificationService { + constructor( + private i18nService: I18nService, + private toastService: ToastService, + ) {} + + handleError(error: unknown, customMessage?: string) { + const message = this.getErrorMessage(error, customMessage); + this.toastService.showToast({ + variant: "error", + title: null, + message, + }); + throw error; // Re-throw to allow caller to handle if needed + } + + showSuccess(messageKey: string) { + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t(messageKey), + }); + } + + private getErrorMessage(error: unknown, customMessage?: string): string { + if (error instanceof ErrorResponse) { + return error.getSingleMessage(); + } + return this.i18nService.t(customMessage ?? "errorOccurred"); + } +} diff --git a/apps/web/src/app/billing/shared/adjust-payment-dialog/adjust-payment-dialog-v2.component.ts b/apps/web/src/app/billing/shared/adjust-payment-dialog/adjust-payment-dialog-v2.component.ts index 0a72b8302bc..4f10adeb5a0 100644 --- a/apps/web/src/app/billing/shared/adjust-payment-dialog/adjust-payment-dialog-v2.component.ts +++ b/apps/web/src/app/billing/shared/adjust-payment-dialog/adjust-payment-dialog-v2.component.ts @@ -1,5 +1,3 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog"; import { Component, forwardRef, Inject, OnInit, ViewChild } from "@angular/core"; @@ -14,8 +12,9 @@ import { PaymentRequest } from "@bitwarden/common/billing/models/request/payment import { UpdatePaymentMethodRequest } from "@bitwarden/common/billing/models/request/update-payment-method.request"; import { TaxInfoResponse } from "@bitwarden/common/billing/models/response/tax-info.response"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { DialogService, ToastService } from "@bitwarden/components"; +import { DialogService } from "@bitwarden/components"; +import { BillingNotificationService } from "../../services/billing-notification.service"; import { PaymentV2Component } from "../payment/payment-v2.component"; export interface AdjustPaymentDialogV2Params { @@ -54,7 +53,7 @@ export class AdjustPaymentDialogV2Component implements OnInit { @Inject(DIALOG_DATA) protected dialogParams: AdjustPaymentDialogV2Params, private dialogRef: DialogRef, private i18nService: I18nService, - private toastService: ToastService, + private notificationService: BillingNotificationService, ) { const key = this.dialogParams.initialPaymentMethod ? "changePaymentMethod" : "addPaymentMethod"; this.dialogHeader = this.i18nService.t(key); @@ -109,30 +108,25 @@ export class AdjustPaymentDialogV2Component implements OnInit { await this.updateOrganizationPaymentMethod(); } - this.toastService.showToast({ - variant: "success", - title: null, - message: this.i18nService.t("updatedPaymentMethod"), - }); - + this.notificationService.showSuccess("updatedPaymentMethod"); this.dialogRef.close(AdjustPaymentDialogV2ResultType.Submitted); } catch (error) { - this.toastService.showToast({ - variant: "error", - title: null, - message: this.i18nService.t(error.message) || error.message, - }); + // Error is already handled by the methods called above } }; private updateOrganizationPaymentMethod = async () => { - const paymentSource = await this.paymentComponent.tokenize(); + try { + const paymentSource = await this.paymentComponent.tokenize(); - const request = new UpdatePaymentMethodRequest(); - request.paymentSource = paymentSource; - request.taxInformation = ExpandedTaxInfoUpdateRequest.From(this.taxInformation); + const request = new UpdatePaymentMethodRequest(); + request.paymentSource = paymentSource; + request.taxInformation = ExpandedTaxInfoUpdateRequest.From(this.taxInformation); - await this.billingApiService.updateOrganizationPaymentMethod(this.organizationId, request); + await this.billingApiService.updateOrganizationPaymentMethod(this.organizationId, request); + } catch (error) { + this.notificationService.handleError(error); + } }; protected get showTaxIdField(): boolean { @@ -150,20 +144,24 @@ export class AdjustPaymentDialogV2Component implements OnInit { } private updatePremiumUserPaymentMethod = async () => { - const { type, token } = await this.paymentComponent.tokenize(); - - const request = new PaymentRequest(); - request.paymentMethodType = type; - request.paymentToken = token; - request.country = this.taxInformation.country; - request.postalCode = this.taxInformation.postalCode; - request.taxId = this.taxInformation.taxId; - request.state = this.taxInformation.state; - request.line1 = this.taxInformation.line1; - request.line2 = this.taxInformation.line2; - request.city = this.taxInformation.city; - request.state = this.taxInformation.state; - await this.apiService.postAccountPayment(request); + try { + const { type, token } = await this.paymentComponent.tokenize(); + + const request = new PaymentRequest(); + request.paymentMethodType = type; + request.paymentToken = token; + request.country = this.taxInformation.country; + request.postalCode = this.taxInformation.postalCode; + request.taxId = this.taxInformation.taxId; + request.state = this.taxInformation.state; + request.line1 = this.taxInformation.line1; + request.line2 = this.taxInformation.line2; + request.city = this.taxInformation.city; + request.state = this.taxInformation.state; + await this.apiService.postAccountPayment(request); + } catch (error) { + this.notificationService.handleError(error); + } }; static open = ( diff --git a/libs/common/src/billing/services/billing-api.service.ts b/libs/common/src/billing/services/billing-api.service.ts index 4306324395e..a76c0493265 100644 --- a/libs/common/src/billing/services/billing-api.service.ts +++ b/libs/common/src/billing/services/billing-api.service.ts @@ -1,24 +1,22 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { ToastService } from "@bitwarden/components"; +import { ProviderOrganizationOrganizationDetailsResponse } from "@bitwarden/common/admin-console/models/response/provider/provider-organization.response"; +import { UpdatePaymentMethodRequest } from "@bitwarden/common/billing/models/request/update-payment-method.request"; +import { VerifyBankAccountRequest } from "@bitwarden/common/billing/models/request/verify-bank-account.request"; +import { InvoicesResponse } from "@bitwarden/common/billing/models/response/invoices.response"; +import { PaymentMethodResponse } from "@bitwarden/common/billing/models/response/payment-method.response"; +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; 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"; import { ExpandedTaxInfoUpdateRequest } from "../models/request/expanded-tax-info-update.request"; import { SubscriptionCancellationRequest } from "../models/request/subscription-cancellation.request"; import { UpdateClientOrganizationRequest } from "../models/request/update-client-organization.request"; -import { UpdatePaymentMethodRequest } from "../models/request/update-payment-method.request"; -import { VerifyBankAccountRequest } from "../models/request/verify-bank-account.request"; -import { InvoicesResponse } from "../models/response/invoices.response"; import { OrganizationBillingMetadataResponse } from "../models/response/organization-billing-metadata.response"; -import { PaymentMethodResponse } from "../models/response/payment-method.response"; import { PlanResponse } from "../models/response/plan.response"; import { ProviderSubscriptionResponse } from "../models/response/provider-subscription-response"; @@ -26,7 +24,6 @@ export class BillingApiService implements BillingApiServiceAbstraction { constructor( private apiService: ApiService, private logService: LogService, - private toastService: ToastService, ) {} cancelOrganizationSubscription( @@ -233,13 +230,6 @@ export class BillingApiService implements BillingApiServiceAbstraction { 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; } } diff --git a/libs/common/tsconfig.json b/libs/common/tsconfig.json index c28b60e28f8..9aa8c4870e2 100644 --- a/libs/common/tsconfig.json +++ b/libs/common/tsconfig.json @@ -6,7 +6,6 @@ "@bitwarden/auth/common": ["../auth/src/common"], // TODO: Remove once circular dependencies in admin-console, auth and key-management are resolved "@bitwarden/common/*": ["../common/src/*"], - "@bitwarden/components": ["../components/src"], "@bitwarden/key-management": ["../key-management/src"], "@bitwarden/platform": ["../platform/src"] }