mirror of
https://github.com/bitwarden/browser
synced 2025-12-15 07:43:35 +00:00
Update client side error handling and remove add account credit from sub (#10214)
This commit is contained in:
@@ -1196,7 +1196,7 @@ const safeProviders: SafeProvider[] = [
|
||||
safeProvider({
|
||||
provide: BillingApiServiceAbstraction,
|
||||
useClass: BillingApiService,
|
||||
deps: [ApiServiceAbstraction],
|
||||
deps: [ApiServiceAbstraction, LogService, ToastService],
|
||||
}),
|
||||
safeProvider({
|
||||
provide: PaymentMethodWarningsServiceAbstraction,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { ProviderOrganizationOrganizationDetailsResponse } from "@bitwarden/common/admin-console/models/response/provider/provider-organization.response";
|
||||
import { PaymentMethodType } from "@bitwarden/common/billing/enums";
|
||||
import { ExpandedTaxInfoUpdateRequest } from "@bitwarden/common/billing/models/request/expanded-tax-info-update.request";
|
||||
import { TokenizedPaymentMethodRequest } from "@bitwarden/common/billing/models/request/tokenized-payment-method.request";
|
||||
@@ -8,7 +9,6 @@ import { PaymentInformationResponse } from "@bitwarden/common/billing/models/res
|
||||
import { SubscriptionCancellationRequest } from "../../billing/models/request/subscription-cancellation.request";
|
||||
import { OrganizationBillingMetadataResponse } from "../../billing/models/response/organization-billing-metadata.response";
|
||||
import { OrganizationBillingStatusResponse } from "../../billing/models/response/organization-billing-status.response";
|
||||
import { OrganizationSubscriptionResponse } from "../../billing/models/response/organization-subscription.response";
|
||||
import { PlanResponse } from "../../billing/models/response/plan.response";
|
||||
import { ListResponse } from "../../models/response/list.response";
|
||||
import { CreateClientOrganizationRequest } from "../models/request/create-client-organization.request";
|
||||
@@ -23,39 +23,45 @@ export abstract class BillingApiServiceAbstraction {
|
||||
|
||||
cancelPremiumUserSubscription: (request: SubscriptionCancellationRequest) => Promise<void>;
|
||||
|
||||
createClientOrganization: (
|
||||
createProviderClientOrganization: (
|
||||
providerId: string,
|
||||
request: CreateClientOrganizationRequest,
|
||||
) => Promise<void>;
|
||||
|
||||
createSetupIntent: (paymentMethodType: PaymentMethodType) => Promise<string>;
|
||||
|
||||
getBillingStatus: (id: string) => Promise<OrganizationBillingStatusResponse>;
|
||||
|
||||
getOrganizationBillingMetadata: (
|
||||
organizationId: string,
|
||||
) => Promise<OrganizationBillingMetadataResponse>;
|
||||
|
||||
getOrganizationSubscription: (
|
||||
organizationId: string,
|
||||
) => Promise<OrganizationSubscriptionResponse>;
|
||||
getOrganizationBillingStatus: (id: string) => Promise<OrganizationBillingStatusResponse>;
|
||||
|
||||
getPlans: () => Promise<ListResponse<PlanResponse>>;
|
||||
|
||||
getProviderClientInvoiceReport: (providerId: string, invoiceId: string) => Promise<string>;
|
||||
|
||||
getProviderClientOrganizations: (
|
||||
providerId: string,
|
||||
) => Promise<ListResponse<ProviderOrganizationOrganizationDetailsResponse>>;
|
||||
|
||||
getProviderInvoices: (providerId: string) => Promise<InvoicesResponse>;
|
||||
|
||||
/**
|
||||
* @deprecated This endpoint is currently deactivated.
|
||||
*/
|
||||
getProviderPaymentInformation: (providerId: string) => Promise<PaymentInformationResponse>;
|
||||
|
||||
getProviderSubscription: (providerId: string) => Promise<ProviderSubscriptionResponse>;
|
||||
|
||||
updateClientOrganization: (
|
||||
updateProviderClientOrganization: (
|
||||
providerId: string,
|
||||
organizationId: string,
|
||||
request: UpdateClientOrganizationRequest,
|
||||
) => Promise<any>;
|
||||
|
||||
/**
|
||||
* @deprecated This endpoint is currently deactivated.
|
||||
*/
|
||||
updateProviderPaymentMethod: (
|
||||
providerId: string,
|
||||
request: TokenizedPaymentMethodRequest,
|
||||
@@ -66,6 +72,9 @@ export abstract class BillingApiServiceAbstraction {
|
||||
request: ExpandedTaxInfoUpdateRequest,
|
||||
) => Promise<void>;
|
||||
|
||||
/**
|
||||
* @deprecated This endpoint is currently deactivated.
|
||||
*/
|
||||
verifyProviderBankAccount: (
|
||||
providerId: string,
|
||||
request: VerifyBankAccountRequest,
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import { ProviderOrganizationOrganizationDetailsResponse } from "@bitwarden/common/admin-console/models/response/provider/provider-organization.response";
|
||||
import { InvoicesResponse } from "@bitwarden/common/billing/models/response/invoices.response";
|
||||
import { ErrorResponse } from "@bitwarden/common/models/response/error.response";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { ToastService } from "@bitwarden/components";
|
||||
|
||||
import { ApiService } from "../../abstractions/api.service";
|
||||
import { BillingApiServiceAbstraction } from "../../billing/abstractions";
|
||||
@@ -9,7 +13,6 @@ import { TokenizedPaymentMethodRequest } from "../../billing/models/request/toke
|
||||
import { VerifyBankAccountRequest } from "../../billing/models/request/verify-bank-account.request";
|
||||
import { OrganizationBillingMetadataResponse } from "../../billing/models/response/organization-billing-metadata.response";
|
||||
import { OrganizationBillingStatusResponse } from "../../billing/models/response/organization-billing-status.response";
|
||||
import { OrganizationSubscriptionResponse } from "../../billing/models/response/organization-subscription.response";
|
||||
import { PaymentInformationResponse } from "../../billing/models/response/payment-information.response";
|
||||
import { PlanResponse } from "../../billing/models/response/plan.response";
|
||||
import { ListResponse } from "../../models/response/list.response";
|
||||
@@ -18,7 +21,11 @@ import { UpdateClientOrganizationRequest } from "../models/request/update-client
|
||||
import { ProviderSubscriptionResponse } from "../models/response/provider-subscription-response";
|
||||
|
||||
export class BillingApiService implements BillingApiServiceAbstraction {
|
||||
constructor(private apiService: ApiService) {}
|
||||
constructor(
|
||||
private apiService: ApiService,
|
||||
private logService: LogService,
|
||||
private toastService: ToastService,
|
||||
) {}
|
||||
|
||||
cancelOrganizationSubscription(
|
||||
organizationId: string,
|
||||
@@ -37,7 +44,7 @@ export class BillingApiService implements BillingApiServiceAbstraction {
|
||||
return this.apiService.send("POST", "/accounts/cancel", request, true, false);
|
||||
}
|
||||
|
||||
createClientOrganization(
|
||||
createProviderClientOrganization(
|
||||
providerId: string,
|
||||
request: CreateClientOrganizationRequest,
|
||||
): Promise<void> {
|
||||
@@ -65,7 +72,7 @@ export class BillingApiService implements BillingApiServiceAbstraction {
|
||||
return response as string;
|
||||
}
|
||||
|
||||
async getBillingStatus(id: string): Promise<OrganizationBillingStatusResponse> {
|
||||
async getOrganizationBillingStatus(id: string): Promise<OrganizationBillingStatusResponse> {
|
||||
const r = await this.apiService.send(
|
||||
"GET",
|
||||
"/organizations/" + id + "/billing-status",
|
||||
@@ -90,19 +97,6 @@ export class BillingApiService implements BillingApiServiceAbstraction {
|
||||
return new OrganizationBillingMetadataResponse(r);
|
||||
}
|
||||
|
||||
async getOrganizationSubscription(
|
||||
organizationId: string,
|
||||
): Promise<OrganizationSubscriptionResponse> {
|
||||
const r = await this.apiService.send(
|
||||
"GET",
|
||||
"/organizations/" + organizationId + "/subscription",
|
||||
null,
|
||||
true,
|
||||
true,
|
||||
);
|
||||
return new OrganizationSubscriptionResponse(r);
|
||||
}
|
||||
|
||||
async getPlans(): Promise<ListResponse<PlanResponse>> {
|
||||
const r = await this.apiService.send("GET", "/plans", null, false, true);
|
||||
return new ListResponse(r, PlanResponse);
|
||||
@@ -119,40 +113,55 @@ export class BillingApiService implements BillingApiServiceAbstraction {
|
||||
return response as string;
|
||||
}
|
||||
|
||||
async getProviderClientOrganizations(
|
||||
providerId: string,
|
||||
): Promise<ListResponse<ProviderOrganizationOrganizationDetailsResponse>> {
|
||||
const response = await this.execute(() =>
|
||||
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.apiService.send(
|
||||
"GET",
|
||||
"/providers/" + providerId + "/billing/invoices",
|
||||
null,
|
||||
true,
|
||||
true,
|
||||
const response = await this.execute(() =>
|
||||
this.apiService.send(
|
||||
"GET",
|
||||
"/providers/" + providerId + "/billing/invoices",
|
||||
null,
|
||||
true,
|
||||
true,
|
||||
),
|
||||
);
|
||||
return new InvoicesResponse(response);
|
||||
}
|
||||
|
||||
async getProviderPaymentInformation(providerId: string): Promise<PaymentInformationResponse> {
|
||||
const response = await this.apiService.send(
|
||||
"GET",
|
||||
"/providers/" + providerId + "/billing/payment-information",
|
||||
null,
|
||||
true,
|
||||
true,
|
||||
const response = await this.execute(() =>
|
||||
this.apiService.send(
|
||||
"GET",
|
||||
"/providers/" + providerId + "/billing/payment-information",
|
||||
null,
|
||||
true,
|
||||
true,
|
||||
),
|
||||
);
|
||||
return new PaymentInformationResponse(response);
|
||||
}
|
||||
|
||||
async getProviderSubscription(providerId: string): Promise<ProviderSubscriptionResponse> {
|
||||
const r = await this.apiService.send(
|
||||
"GET",
|
||||
"/providers/" + providerId + "/billing/subscription",
|
||||
null,
|
||||
true,
|
||||
true,
|
||||
const response = await this.execute(() =>
|
||||
this.apiService.send(
|
||||
"GET",
|
||||
"/providers/" + providerId + "/billing/subscription",
|
||||
null,
|
||||
true,
|
||||
true,
|
||||
),
|
||||
);
|
||||
return new ProviderSubscriptionResponse(r);
|
||||
return new ProviderSubscriptionResponse(response);
|
||||
}
|
||||
|
||||
async updateClientOrganization(
|
||||
async updateProviderClientOrganization(
|
||||
providerId: string,
|
||||
organizationId: string,
|
||||
request: UpdateClientOrganizationRequest,
|
||||
@@ -198,4 +207,20 @@ 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,13 +113,13 @@ describe("Payment Method Warnings Service", () => {
|
||||
};
|
||||
activeUserState.nextState(state);
|
||||
await paymentMethodWarningsService.update(organizationId);
|
||||
expect(billingApiService.getBillingStatus).not.toHaveBeenCalled();
|
||||
expect(billingApiService.getOrganizationBillingStatus).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("Retrieves the billing status from the API and uses it to update the state if the state is null", async () => {
|
||||
const organizationId = "1";
|
||||
activeUserState.nextState(null);
|
||||
billingApiService.getBillingStatus.mockResolvedValue(
|
||||
billingApiService.getOrganizationBillingStatus.mockResolvedValue(
|
||||
getBillingStatusResponse(organizationId),
|
||||
);
|
||||
await paymentMethodWarningsService.update(organizationId);
|
||||
@@ -131,7 +131,7 @@ describe("Payment Method Warnings Service", () => {
|
||||
savedAt: any(),
|
||||
},
|
||||
});
|
||||
expect(billingApiService.getBillingStatus).toHaveBeenCalledTimes(1);
|
||||
expect(billingApiService.getOrganizationBillingStatus).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("Retrieves the billing status from the API and uses it to update the state if the stored warning is null", async () => {
|
||||
@@ -139,7 +139,7 @@ describe("Payment Method Warnings Service", () => {
|
||||
activeUserState.nextState({
|
||||
[organizationId]: null,
|
||||
});
|
||||
billingApiService.getBillingStatus.mockResolvedValue(
|
||||
billingApiService.getOrganizationBillingStatus.mockResolvedValue(
|
||||
getBillingStatusResponse(organizationId),
|
||||
);
|
||||
await paymentMethodWarningsService.update(organizationId);
|
||||
@@ -151,7 +151,7 @@ describe("Payment Method Warnings Service", () => {
|
||||
savedAt: any(),
|
||||
},
|
||||
});
|
||||
expect(billingApiService.getBillingStatus).toHaveBeenCalledTimes(1);
|
||||
expect(billingApiService.getOrganizationBillingStatus).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("Retrieves the billing status from the API and uses it to update the state if the stored warning is older than a week", async () => {
|
||||
@@ -164,7 +164,7 @@ describe("Payment Method Warnings Service", () => {
|
||||
savedAt: getPastDate(10),
|
||||
},
|
||||
});
|
||||
billingApiService.getBillingStatus.mockResolvedValue(
|
||||
billingApiService.getOrganizationBillingStatus.mockResolvedValue(
|
||||
new OrganizationBillingStatusResponse({
|
||||
OrganizationId: organizationId,
|
||||
OrganizationName: "Teams Organization",
|
||||
@@ -180,7 +180,7 @@ describe("Payment Method Warnings Service", () => {
|
||||
savedAt: any(),
|
||||
},
|
||||
});
|
||||
expect(billingApiService.getBillingStatus).toHaveBeenCalledTimes(1);
|
||||
expect(billingApiService.getOrganizationBillingStatus).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -52,7 +52,7 @@ export class PaymentMethodWarningsService implements PaymentMethodWarningsServic
|
||||
);
|
||||
if (!warning || warning.savedAt < this.getOneWeekAgo()) {
|
||||
const { organizationName, risksSubscriptionFailure } =
|
||||
await this.billingApiService.getBillingStatus(organizationId);
|
||||
await this.billingApiService.getOrganizationBillingStatus(organizationId);
|
||||
await this.paymentMethodWarningsState.update((state) => {
|
||||
state ??= {};
|
||||
state[organizationId] = {
|
||||
|
||||
Reference in New Issue
Block a user