1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-10 13:23:34 +00:00

[PM-22415] Tax ID notifications for Organizations and Providers (#15996)

* [NO LOGIC] Rename BillableEntity to BitwardenSubscriber

This helps us maintain paraody with server where we call this choice type ISubscriber. I chose BitwardenSubscriber to avoid overlap with RxJS

* [NO LOGIC] Move subscriber-billing.client to clients folder

* [NO LOGIC] Move organization warnings under organization folder

* Move getWarnings from OrganizationBillingApiService to new OrganizationBillingClient

I'd like us to move away from stashing so much in libs and utilizing the JsLibServicesModule when it's not necessary to do so. These are invocations used exclusively by the Web Vault and, until that changes, they should be treated as such

* Refactor OrganizationWarningsService

There was a case added to the Inactive Subscription warning for a free trial, but free trials do not represent inactive subscriptions so this was semantically incorrect. This creates another method that pulls the free trial warning and shows a dialog asking the user to subscribe if they're on one.

* Implement Tax ID Warnings throughout Admin Console and Provider Portal

* Fix linting error

* Jimmy's feedback
This commit is contained in:
Alex Morask
2025-08-18 09:52:28 -05:00
committed by GitHub
parent 0c166b3f94
commit df7f1a8d49
58 changed files with 2422 additions and 976 deletions

View File

@@ -1,5 +1,4 @@
import { ChangePlanFrequencyRequest } from "@bitwarden/common/billing/models/request/change-plan-frequency.request";
import { OrganizationWarningsResponse } from "@bitwarden/common/billing/models/response/organization-warnings.response";
import {
BillingInvoiceResponse,
@@ -18,8 +17,6 @@ export abstract class OrganizationBillingApiServiceAbstraction {
startAfter?: string,
) => Promise<BillingTransactionResponse[]>;
abstract getWarnings: (id: string) => Promise<OrganizationWarningsResponse>;
abstract setupBusinessUnit: (
id: string,
request: {

View File

@@ -1,97 +0,0 @@
import { BaseResponse } from "../../../models/response/base.response";
export class OrganizationWarningsResponse extends BaseResponse {
freeTrial?: FreeTrialWarningResponse;
inactiveSubscription?: InactiveSubscriptionWarningResponse;
resellerRenewal?: ResellerRenewalWarningResponse;
constructor(response: any) {
super(response);
const freeTrialWarning = this.getResponseProperty("FreeTrial");
if (freeTrialWarning) {
this.freeTrial = new FreeTrialWarningResponse(freeTrialWarning);
}
const inactiveSubscriptionWarning = this.getResponseProperty("InactiveSubscription");
if (inactiveSubscriptionWarning) {
this.inactiveSubscription = new InactiveSubscriptionWarningResponse(
inactiveSubscriptionWarning,
);
}
const resellerWarning = this.getResponseProperty("ResellerRenewal");
if (resellerWarning) {
this.resellerRenewal = new ResellerRenewalWarningResponse(resellerWarning);
}
}
}
class FreeTrialWarningResponse extends BaseResponse {
remainingTrialDays: number;
constructor(response: any) {
super(response);
this.remainingTrialDays = this.getResponseProperty("RemainingTrialDays");
}
}
class InactiveSubscriptionWarningResponse extends BaseResponse {
resolution: string;
constructor(response: any) {
super(response);
this.resolution = this.getResponseProperty("Resolution");
}
}
class ResellerRenewalWarningResponse extends BaseResponse {
type: "upcoming" | "issued" | "past_due";
upcoming?: UpcomingRenewal;
issued?: IssuedRenewal;
pastDue?: PastDueRenewal;
constructor(response: any) {
super(response);
this.type = this.getResponseProperty("Type");
switch (this.type) {
case "upcoming": {
this.upcoming = new UpcomingRenewal(this.getResponseProperty("Upcoming"));
break;
}
case "issued": {
this.issued = new IssuedRenewal(this.getResponseProperty("Issued"));
break;
}
case "past_due": {
this.pastDue = new PastDueRenewal(this.getResponseProperty("PastDue"));
}
}
}
}
class UpcomingRenewal extends BaseResponse {
renewalDate: Date;
constructor(response: any) {
super(response);
this.renewalDate = new Date(this.getResponseProperty("RenewalDate"));
}
}
class IssuedRenewal extends BaseResponse {
issuedDate: Date;
dueDate: Date;
constructor(response: any) {
super(response);
this.issuedDate = new Date(this.getResponseProperty("IssuedDate"));
this.dueDate = new Date(this.getResponseProperty("DueDate"));
}
}
class PastDueRenewal extends BaseResponse {
suspensionDate: Date;
constructor(response: any) {
super(response);
this.suspensionDate = new Date(this.getResponseProperty("SuspensionDate"));
}
}

View File

@@ -1,5 +1,4 @@
import { ChangePlanFrequencyRequest } from "@bitwarden/common/billing/models/request/change-plan-frequency.request";
import { OrganizationWarningsResponse } from "@bitwarden/common/billing/models/response/organization-warnings.response";
import { ApiService } from "../../../abstractions/api.service";
import { OrganizationBillingApiServiceAbstraction } from "../../abstractions/organizations/organization-billing-api.service.abstraction";
@@ -53,18 +52,6 @@ export class OrganizationBillingApiService implements OrganizationBillingApiServ
return r?.map((i: any) => new BillingTransactionResponse(i)) || [];
}
async getWarnings(id: string): Promise<OrganizationWarningsResponse> {
const response = await this.apiService.send(
"GET",
`/organizations/${id}/billing/warnings`,
null,
true,
true,
);
return new OrganizationWarningsResponse(response);
}
async setupBusinessUnit(
id: string,
request: {

View File

@@ -32,6 +32,7 @@ export enum FeatureFlag {
AllowTrialLengthZero = "pm-20322-allow-trial-length-0",
PM21881_ManagePaymentDetailsOutsideCheckout = "pm-21881-manage-payment-details-outside-checkout",
PM21821_ProviderPortalTakeover = "pm-21821-provider-portal-takeover",
PM22415_TaxIDWarnings = "pm-22415-tax-id-warnings",
/* Key Management */
PrivateKeyRegeneration = "pm-12241-private-key-regeneration",
@@ -108,6 +109,7 @@ export const DefaultFeatureFlagValue = {
[FeatureFlag.AllowTrialLengthZero]: FALSE,
[FeatureFlag.PM21881_ManagePaymentDetailsOutsideCheckout]: FALSE,
[FeatureFlag.PM21821_ProviderPortalTakeover]: FALSE,
[FeatureFlag.PM22415_TaxIDWarnings]: FALSE,
/* Key Management */
[FeatureFlag.PrivateKeyRegeneration]: FALSE,