From 1e0ad097577f29a2ad303404a68884fe940fbc42 Mon Sep 17 00:00:00 2001 From: Alex Morask <144709477+amorask-bitwarden@users.noreply.github.com> Date: Thu, 18 Apr 2024 08:36:38 -0400 Subject: [PATCH] Added create-client-organization.component (#8767) --- apps/web/src/locales/en/messages.json | 21 +++ .../providers/providers-routing.module.ts | 2 +- .../providers/providers.module.ts | 10 +- .../services/web-provider.service.ts | 50 ++++++ .../create-client-organization.component.html | 69 +++++++++ .../create-client-organization.component.ts | 142 ++++++++++++++++++ .../app/billing/providers/clients/index.ts | 3 + ...ent-organization-subscription.component.ts | 8 +- ...manage-client-organizations.component.html | 8 +- .../manage-client-organizations.component.ts | 35 ++++- .../src/services/jslib-services.module.ts | 2 + .../billilng-api.service.abstraction.ts | 16 +- .../create-client-organization.request.ts | 12 ++ .../provider-subscription-update.request.ts | 3 - .../update-client-organization.request.ts | 3 + .../billing/services/billing-api.service.ts | 31 +++- .../services/organization-billing.service.ts | 20 ++- 17 files changed, 409 insertions(+), 26 deletions(-) create mode 100644 bitwarden_license/bit-web/src/app/billing/providers/clients/create-client-organization.component.html create mode 100644 bitwarden_license/bit-web/src/app/billing/providers/clients/create-client-organization.component.ts create mode 100644 bitwarden_license/bit-web/src/app/billing/providers/clients/index.ts create mode 100644 libs/common/src/billing/models/request/create-client-organization.request.ts delete mode 100644 libs/common/src/billing/models/request/provider-subscription-update.request.ts create mode 100644 libs/common/src/billing/models/request/update-client-organization.request.ts diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index 32c748d4af..49611145dd 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -7958,5 +7958,26 @@ }, "errorAssigningTargetFolder": { "message": "Error assigning target folder." + }, + "createNewClientToManageAsProvider": { + "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." + }, + "selectAPlan": { + "message": "Select a plan" + }, + "thirtyFivePercentDiscount": { + "message": "35% Discount" + }, + "monthPerMember": { + "message": "month per member" + }, + "seats": { + "message": "Seats" + }, + "addOrganization": { + "message": "Add organization" + }, + "createdNewClient": { + "message": "Successfully created new client" } } diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/providers-routing.module.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/providers-routing.module.ts index a42b10d88f..3976499268 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/providers-routing.module.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/providers-routing.module.ts @@ -69,7 +69,7 @@ const routes: Routes = [ { path: "manage-client-organizations", component: ManageClientOrganizationsComponent, - data: { titleId: "manage-client-organizations" }, + data: { titleId: "clients" }, }, { path: "manage", diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/providers.module.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/providers.module.ts index 0d75973712..20350fc600 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/providers.module.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/providers.module.ts @@ -4,13 +4,16 @@ import { FormsModule } from "@angular/forms"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { SearchModule } from "@bitwarden/components"; +import { DangerZoneComponent } from "@bitwarden/web-vault/app/auth/settings/account/danger-zone.component"; import { OrganizationPlansComponent, TaxInfoComponent } from "@bitwarden/web-vault/app/billing"; import { PaymentMethodWarningsModule } from "@bitwarden/web-vault/app/billing/shared"; import { OssModule } from "@bitwarden/web-vault/app/oss.module"; -import { DangerZoneComponent } from "../../../../../../apps/web/src/app/auth/settings/account/danger-zone.component"; -import { ManageClientOrganizationSubscriptionComponent } from "../../billing/providers/clients/manage-client-organization-subscription.component"; -import { ManageClientOrganizationsComponent } from "../../billing/providers/clients/manage-client-organizations.component"; +import { + CreateClientOrganizationComponent, + ManageClientOrganizationSubscriptionComponent, + ManageClientOrganizationsComponent, +} from "../../billing/providers/clients"; import { AddOrganizationComponent } from "./clients/add-organization.component"; import { ClientsComponent } from "./clients/clients.component"; @@ -56,6 +59,7 @@ import { SetupComponent } from "./setup/setup.component"; SetupComponent, SetupProviderComponent, UserAddEditComponent, + CreateClientOrganizationComponent, ManageClientOrganizationsComponent, ManageClientOrganizationSubscriptionComponent, ], diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/services/web-provider.service.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/services/web-provider.service.ts index 4f715fd5c5..4195ffcb05 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/services/web-provider.service.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/services/web-provider.service.ts @@ -1,8 +1,15 @@ import { Injectable } from "@angular/core"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { OrganizationKeysRequest } from "@bitwarden/common/admin-console/models/request/organization-keys.request"; import { ProviderAddOrganizationRequest } from "@bitwarden/common/admin-console/models/request/provider/provider-add-organization.request"; +import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/billilng-api.service.abstraction"; +import { PlanType } from "@bitwarden/common/billing/enums"; +import { CreateClientOrganizationRequest } from "@bitwarden/common/billing/models/request/create-client-organization.request"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; +import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { OrgKey } from "@bitwarden/common/types/key"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; @Injectable() @@ -11,6 +18,9 @@ export class WebProviderService { private cryptoService: CryptoService, private syncService: SyncService, private apiService: ApiService, + private i18nService: I18nService, + private encryptService: EncryptService, + private billingApiService: BillingApiServiceAbstraction, ) {} async addOrganizationToProvider(providerId: string, organizationId: string) { @@ -28,6 +38,46 @@ export class WebProviderService { return response; } + async createClientOrganization( + providerId: string, + name: string, + ownerEmail: string, + planType: PlanType, + seats: number, + ): Promise { + const organizationKey = (await this.cryptoService.makeOrgKey())[1]; + + const [publicKey, encryptedPrivateKey] = await this.cryptoService.makeKeyPair(organizationKey); + + const encryptedCollectionName = await this.encryptService.encrypt( + this.i18nService.t("defaultCollection"), + organizationKey, + ); + + const providerKey = await this.cryptoService.getProviderKey(providerId); + + const encryptedProviderKey = await this.encryptService.encrypt( + organizationKey.key, + providerKey, + ); + + const request = new CreateClientOrganizationRequest(); + request.name = name; + request.ownerEmail = ownerEmail; + request.planType = planType; + request.seats = seats; + + request.key = encryptedProviderKey.encryptedString; + request.keyPair = new OrganizationKeysRequest(publicKey, encryptedPrivateKey.encryptedString); + request.collectionName = encryptedCollectionName.encryptedString; + + await this.billingApiService.createClientOrganization(providerId, request); + + await this.apiService.refreshIdentityToken(); + + await this.syncService.fullSync(true); + } + async detachOrganization(providerId: string, organizationId: string): Promise { await this.apiService.deleteProviderOrganization(providerId, organizationId); await this.syncService.fullSync(true); diff --git a/bitwarden_license/bit-web/src/app/billing/providers/clients/create-client-organization.component.html b/bitwarden_license/bit-web/src/app/billing/providers/clients/create-client-organization.component.html new file mode 100644 index 0000000000..4c5d9fca9b --- /dev/null +++ b/bitwarden_license/bit-web/src/app/billing/providers/clients/create-client-organization.component.html @@ -0,0 +1,69 @@ +
+ + + {{ "newClientOrganization" | i18n }} + +
+

{{ "createNewClientToManageAsProvider" | i18n }}

+
+ {{ "selectAPlan" | i18n }} + {{ "thirtyFivePercentDiscount" | i18n }} +
+ +
+
+
+
+ {{ "selected" | i18n }} +
+
+

{{ planCard.name }}

+ {{ + planCard.cost | currency: "$" + }} + /{{ "monthPerMember" | i18n }} +
+
+
+
+
+
+ + + {{ "organizationName" | i18n }} + + + + + + {{ "clientOwnerEmail" | i18n }} + + + +
+
+ + + {{ "seats" | i18n }} + + + +
+
+ + + + +
+
diff --git a/bitwarden_license/bit-web/src/app/billing/providers/clients/create-client-organization.component.ts b/bitwarden_license/bit-web/src/app/billing/providers/clients/create-client-organization.component.ts new file mode 100644 index 0000000000..8427572516 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/billing/providers/clients/create-client-organization.component.ts @@ -0,0 +1,142 @@ +import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog"; +import { Component, Inject, OnInit } from "@angular/core"; +import { FormBuilder, Validators } from "@angular/forms"; + +import { PlanType } from "@bitwarden/common/billing/enums"; +import { PlanResponse } from "@bitwarden/common/billing/models/response/plan.response"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { DialogService } from "@bitwarden/components"; + +import { WebProviderService } from "../../../admin-console/providers/services/web-provider.service"; + +type CreateClientOrganizationParams = { + providerId: string; + plans: PlanResponse[]; +}; + +export enum CreateClientOrganizationResultType { + Closed = "closed", + Submitted = "submitted", +} + +export const openCreateClientOrganizationDialog = ( + dialogService: DialogService, + dialogConfig: DialogConfig, +) => + dialogService.open( + CreateClientOrganizationComponent, + dialogConfig, + ); + +type PlanCard = { + name: string; + cost: number; + type: PlanType; + selected: boolean; +}; + +@Component({ + selector: "app-create-client-organization", + templateUrl: "./create-client-organization.component.html", +}) +export class CreateClientOrganizationComponent implements OnInit { + protected ResultType = CreateClientOrganizationResultType; + protected formGroup = this.formBuilder.group({ + clientOwnerEmail: ["", [Validators.required, Validators.email]], + organizationName: ["", Validators.required], + seats: [null, [Validators.required, Validators.min(1)]], + }); + protected planCards: PlanCard[]; + + constructor( + @Inject(DIALOG_DATA) private dialogParams: CreateClientOrganizationParams, + private dialogRef: DialogRef, + private formBuilder: FormBuilder, + private i18nService: I18nService, + private platformUtilsService: PlatformUtilsService, + private webProviderService: WebProviderService, + ) {} + + protected getPlanCardContainerClasses(selected: boolean) { + switch (selected) { + case true: { + return [ + "tw-group", + "tw-cursor-pointer", + "tw-block", + "tw-rounded", + "tw-border", + "tw-border-solid", + "tw-border-primary-600", + "hover:tw-border-primary-700", + "focus:tw-border-2", + "focus:tw-border-primary-700", + "focus:tw-rounded-lg", + ]; + } + case false: { + return [ + "tw-cursor-pointer", + "tw-block", + "tw-rounded", + "tw-border", + "tw-border-solid", + "tw-border-secondary-300", + "hover:tw-border-text-main", + "focus:tw-border-2", + "focus:tw-border-primary-700", + ]; + } + } + } + + async ngOnInit(): Promise { + const teamsPlan = this.dialogParams.plans.find((plan) => plan.type === PlanType.TeamsMonthly); + const enterprisePlan = this.dialogParams.plans.find( + (plan) => plan.type === PlanType.EnterpriseMonthly, + ); + + this.planCards = [ + { + name: this.i18nService.t("planNameTeams"), + cost: teamsPlan.PasswordManager.seatPrice * 0.65, // 35% off for MSPs, + type: teamsPlan.type, + selected: true, + }, + { + name: this.i18nService.t("planNameEnterprise"), + cost: enterprisePlan.PasswordManager.seatPrice * 0.65, // 35% off for MSPs, + type: enterprisePlan.type, + selected: false, + }, + ]; + } + + protected selectPlan(name: string) { + this.planCards.find((planCard) => planCard.name === name).selected = true; + this.planCards.find((planCard) => planCard.name !== name).selected = false; + } + + submit = async () => { + this.formGroup.markAllAsTouched(); + + if (this.formGroup.invalid) { + return; + } + + const selectedPlanCard = this.planCards.find((planCard) => planCard.selected); + + await this.webProviderService.createClientOrganization( + this.dialogParams.providerId, + this.formGroup.value.organizationName, + this.formGroup.value.clientOwnerEmail, + selectedPlanCard.type, + this.formGroup.value.seats, + ); + + this.platformUtilsService.showToast("success", null, this.i18nService.t("createdNewClient")); + + this.dialogRef.close(this.ResultType.Submitted); + }; +} diff --git a/bitwarden_license/bit-web/src/app/billing/providers/clients/index.ts b/bitwarden_license/bit-web/src/app/billing/providers/clients/index.ts new file mode 100644 index 0000000000..fd9ef8296c --- /dev/null +++ b/bitwarden_license/bit-web/src/app/billing/providers/clients/index.ts @@ -0,0 +1,3 @@ +export * from "./create-client-organization.component"; +export * from "./manage-client-organizations.component"; +export * from "./manage-client-organization-subscription.component"; diff --git a/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-organization-subscription.component.ts b/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-organization-subscription.component.ts index 2c8d59edc3..2182ac43ab 100644 --- a/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-organization-subscription.component.ts +++ b/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-organization-subscription.component.ts @@ -3,7 +3,7 @@ import { Component, Inject, OnInit } from "@angular/core"; import { ProviderOrganizationOrganizationDetailsResponse } from "@bitwarden/common/admin-console/models/response/provider/provider-organization.response"; import { BillingApiServiceAbstraction as BillingApiService } from "@bitwarden/common/billing/abstractions/billilng-api.service.abstraction"; -import { ProviderSubscriptionUpdateRequest } from "@bitwarden/common/billing/models/request/provider-subscription-update.request"; +import { UpdateClientOrganizationRequest } from "@bitwarden/common/billing/models/request/update-client-organization.request"; import { Plans } from "@bitwarden/common/billing/models/response/provider-subscription-response"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; @@ -45,7 +45,7 @@ export class ManageClientOrganizationSubscriptionComponent implements OnInit { async ngOnInit() { try { - const response = await this.billingApiService.getProviderClientSubscriptions(this.providerId); + const response = await this.billingApiService.getProviderSubscription(this.providerId); this.AdditionalSeatPurchased = this.getPurchasedSeatsByPlan(this.planName, response.plans); const seatMinimum = this.getProviderSeatMinimumByPlan(this.planName, response.plans); const assignedByPlan = this.getAssignedByPlan(this.planName, response.plans); @@ -69,10 +69,10 @@ export class ManageClientOrganizationSubscriptionComponent implements OnInit { return; } - const request = new ProviderSubscriptionUpdateRequest(); + const request = new UpdateClientOrganizationRequest(); request.assignedSeats = assignedSeats; - await this.billingApiService.putProviderClientSubscriptions( + await this.billingApiService.updateClientOrganization( this.providerId, this.providerOrganizationId, request, diff --git a/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-organizations.component.html b/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-organizations.component.html index dc303d338f..ec5df609c4 100644 --- a/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-organizations.component.html +++ b/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-organizations.component.html @@ -1,6 +1,12 @@ - + {{ "addNewOrganization" | i18n }} diff --git a/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-organizations.component.ts b/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-organizations.component.ts index a9f341be94..2184a617cf 100644 --- a/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-organizations.component.ts +++ b/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-organizations.component.ts @@ -1,7 +1,7 @@ import { SelectionModel } from "@angular/cdk/collections"; import { Component, OnDestroy, OnInit } from "@angular/core"; import { ActivatedRoute } from "@angular/router"; -import { BehaviorSubject, Subject, firstValueFrom, from } from "rxjs"; +import { BehaviorSubject, firstValueFrom, from, lastValueFrom, Subject } from "rxjs"; import { first, switchMap, takeUntil } from "rxjs/operators"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; @@ -9,6 +9,8 @@ import { SearchService } from "@bitwarden/common/abstractions/search.service"; import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service"; import { ProviderUserType } from "@bitwarden/common/admin-console/enums"; import { ProviderOrganizationOrganizationDetailsResponse } from "@bitwarden/common/admin-console/models/response/provider/provider-organization.response"; +import { BillingApiServiceAbstraction as BillingApiService } from "@bitwarden/common/billing/abstractions/billilng-api.service.abstraction"; +import { PlanResponse } from "@bitwarden/common/billing/models/response/plan.response"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service"; @@ -16,6 +18,10 @@ import { DialogService, TableDataSource } from "@bitwarden/components"; import { WebProviderService } from "../../../admin-console/providers/services/web-provider.service"; +import { + CreateClientOrganizationResultType, + openCreateClientOrganizationDialog, +} from "./create-client-organization.component"; import { ManageClientOrganizationSubscriptionComponent } from "./manage-client-organization-subscription.component"; @Component({ @@ -52,6 +58,7 @@ export class ManageClientOrganizationsComponent implements OnInit, OnDestroy { private pagedClientsCount = 0; selection = new SelectionModel(true, []); protected dataSource = new TableDataSource(); + protected plans: PlanResponse[]; constructor( private route: ActivatedRoute, @@ -63,6 +70,7 @@ export class ManageClientOrganizationsComponent implements OnInit, OnDestroy { private validationService: ValidationService, private webProviderService: WebProviderService, private dialogService: DialogService, + private billingApiService: BillingApiService, ) {} async ngOnInit() { @@ -94,12 +102,16 @@ export class ManageClientOrganizationsComponent implements OnInit, OnDestroy { } async load() { - const response = await this.apiService.getProviderClients(this.providerId); - this.clients = response.data != null && response.data.length > 0 ? response.data : []; + const clientsResponse = await this.apiService.getProviderClients(this.providerId); + this.clients = + clientsResponse.data != null && clientsResponse.data.length > 0 ? clientsResponse.data : []; this.dataSource.data = this.clients; this.manageOrganizations = (await this.providerService.get(this.providerId)).type === ProviderUserType.ProviderAdmin; + const plansResponse = await this.billingApiService.getPlans(); + this.plans = plansResponse.data; + this.loading = false; } @@ -177,4 +189,21 @@ export class ManageClientOrganizationsComponent implements OnInit, OnDestroy { } this.actionPromise = null; } + + createClientOrganization = async () => { + const reference = openCreateClientOrganizationDialog(this.dialogService, { + data: { + providerId: this.providerId, + plans: this.plans, + }, + }); + + const result = await lastValueFrom(reference.closed); + + if (result === CreateClientOrganizationResultType.Closed) { + return; + } + + await this.load(); + }; } diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index ad0881a4b3..cc6d947b33 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -1051,10 +1051,12 @@ const safeProviders: SafeProvider[] = [ provide: OrganizationBillingServiceAbstraction, useClass: OrganizationBillingService, deps: [ + ApiServiceAbstraction, CryptoServiceAbstraction, EncryptService, I18nServiceAbstraction, OrganizationApiServiceAbstraction, + SyncServiceAbstraction, ], }), safeProvider({ diff --git a/libs/common/src/billing/abstractions/billilng-api.service.abstraction.ts b/libs/common/src/billing/abstractions/billilng-api.service.abstraction.ts index 1311976c4b..16b86c5844 100644 --- a/libs/common/src/billing/abstractions/billilng-api.service.abstraction.ts +++ b/libs/common/src/billing/abstractions/billilng-api.service.abstraction.ts @@ -1,6 +1,9 @@ import { SubscriptionCancellationRequest } from "../../billing/models/request/subscription-cancellation.request"; import { OrganizationBillingStatusResponse } from "../../billing/models/response/organization-billing-status.response"; -import { ProviderSubscriptionUpdateRequest } from "../models/request/provider-subscription-update.request"; +import { PlanResponse } from "../../billing/models/response/plan.response"; +import { ListResponse } from "../../models/response/list.response"; +import { CreateClientOrganizationRequest } from "../models/request/create-client-organization.request"; +import { UpdateClientOrganizationRequest } from "../models/request/update-client-organization.request"; import { ProviderSubscriptionResponse } from "../models/response/provider-subscription-response"; export abstract class BillingApiServiceAbstraction { @@ -9,11 +12,16 @@ export abstract class BillingApiServiceAbstraction { request: SubscriptionCancellationRequest, ) => Promise; cancelPremiumUserSubscription: (request: SubscriptionCancellationRequest) => Promise; + createClientOrganization: ( + providerId: string, + request: CreateClientOrganizationRequest, + ) => Promise; getBillingStatus: (id: string) => Promise; - getProviderClientSubscriptions: (providerId: string) => Promise; - putProviderClientSubscriptions: ( + getPlans: () => Promise>; + getProviderSubscription: (providerId: string) => Promise; + updateClientOrganization: ( providerId: string, organizationId: string, - request: ProviderSubscriptionUpdateRequest, + request: UpdateClientOrganizationRequest, ) => Promise; } diff --git a/libs/common/src/billing/models/request/create-client-organization.request.ts b/libs/common/src/billing/models/request/create-client-organization.request.ts new file mode 100644 index 0000000000..2eac23531a --- /dev/null +++ b/libs/common/src/billing/models/request/create-client-organization.request.ts @@ -0,0 +1,12 @@ +import { OrganizationKeysRequest } from "../../../admin-console/models/request/organization-keys.request"; +import { PlanType } from "../../../billing/enums"; + +export class CreateClientOrganizationRequest { + name: string; + ownerEmail: string; + planType: PlanType; + seats: number; + key: string; + keyPair: OrganizationKeysRequest; + collectionName: string; +} diff --git a/libs/common/src/billing/models/request/provider-subscription-update.request.ts b/libs/common/src/billing/models/request/provider-subscription-update.request.ts deleted file mode 100644 index f2bf4c7e97..0000000000 --- a/libs/common/src/billing/models/request/provider-subscription-update.request.ts +++ /dev/null @@ -1,3 +0,0 @@ -export class ProviderSubscriptionUpdateRequest { - assignedSeats: number; -} diff --git a/libs/common/src/billing/models/request/update-client-organization.request.ts b/libs/common/src/billing/models/request/update-client-organization.request.ts new file mode 100644 index 0000000000..16dbe1e17d --- /dev/null +++ b/libs/common/src/billing/models/request/update-client-organization.request.ts @@ -0,0 +1,3 @@ +export class UpdateClientOrganizationRequest { + assignedSeats: number; +} diff --git a/libs/common/src/billing/services/billing-api.service.ts b/libs/common/src/billing/services/billing-api.service.ts index 48866ab90d..70d1d89252 100644 --- a/libs/common/src/billing/services/billing-api.service.ts +++ b/libs/common/src/billing/services/billing-api.service.ts @@ -2,7 +2,10 @@ import { ApiService } from "../../abstractions/api.service"; import { BillingApiServiceAbstraction } from "../../billing/abstractions/billilng-api.service.abstraction"; import { SubscriptionCancellationRequest } from "../../billing/models/request/subscription-cancellation.request"; import { OrganizationBillingStatusResponse } from "../../billing/models/response/organization-billing-status.response"; -import { ProviderSubscriptionUpdateRequest } from "../models/request/provider-subscription-update.request"; +import { PlanResponse } from "../../billing/models/response/plan.response"; +import { ListResponse } from "../../models/response/list.response"; +import { CreateClientOrganizationRequest } from "../models/request/create-client-organization.request"; +import { UpdateClientOrganizationRequest } from "../models/request/update-client-organization.request"; import { ProviderSubscriptionResponse } from "../models/response/provider-subscription-response"; export class BillingApiService implements BillingApiServiceAbstraction { @@ -25,6 +28,19 @@ export class BillingApiService implements BillingApiServiceAbstraction { return this.apiService.send("POST", "/accounts/cancel", request, true, false); } + createClientOrganization( + providerId: string, + request: CreateClientOrganizationRequest, + ): Promise { + return this.apiService.send( + "POST", + "/providers/" + providerId + "/clients", + request, + true, + false, + ); + } + async getBillingStatus(id: string): Promise { const r = await this.apiService.send( "GET", @@ -37,7 +53,12 @@ export class BillingApiService implements BillingApiServiceAbstraction { return new OrganizationBillingStatusResponse(r); } - async getProviderClientSubscriptions(providerId: string): Promise { + async getPlans(): Promise> { + const r = await this.apiService.send("GET", "/plans", null, false, true); + return new ListResponse(r, PlanResponse); + } + + async getProviderSubscription(providerId: string): Promise { const r = await this.apiService.send( "GET", "/providers/" + providerId + "/billing/subscription", @@ -48,14 +69,14 @@ export class BillingApiService implements BillingApiServiceAbstraction { return new ProviderSubscriptionResponse(r); } - async putProviderClientSubscriptions( + async updateClientOrganization( providerId: string, organizationId: string, - request: ProviderSubscriptionUpdateRequest, + request: UpdateClientOrganizationRequest, ): Promise { return await this.apiService.send( "PUT", - "/providers/" + providerId + "/organizations/" + organizationId, + "/providers/" + providerId + "/clients/" + organizationId, request, true, false, diff --git a/libs/common/src/billing/services/organization-billing.service.ts b/libs/common/src/billing/services/organization-billing.service.ts index f2df30e4e0..6b326472c9 100644 --- a/libs/common/src/billing/services/organization-billing.service.ts +++ b/libs/common/src/billing/services/organization-billing.service.ts @@ -1,3 +1,4 @@ +import { ApiService } from "../../abstractions/api.service"; import { OrganizationApiServiceAbstraction as OrganizationApiService } from "../../admin-console/abstractions/organization/organization-api.service.abstraction"; import { OrganizationCreateRequest } from "../../admin-console/models/request/organization-create.request"; import { OrganizationKeysRequest } from "../../admin-console/models/request/organization-keys.request"; @@ -7,6 +8,7 @@ import { EncryptService } from "../../platform/abstractions/encrypt.service"; import { I18nService } from "../../platform/abstractions/i18n.service"; import { EncString } from "../../platform/models/domain/enc-string"; import { OrgKey } from "../../types/key"; +import { SyncService } from "../../vault/abstractions/sync/sync.service.abstraction"; import { OrganizationBillingServiceAbstraction, OrganizationInformation, @@ -25,10 +27,12 @@ interface OrganizationKeys { export class OrganizationBillingService implements OrganizationBillingServiceAbstraction { constructor( + private apiService: ApiService, private cryptoService: CryptoService, private encryptService: EncryptService, private i18nService: I18nService, private organizationApiService: OrganizationApiService, + private syncService: SyncService, ) {} async purchaseSubscription(subscription: SubscriptionInformation): Promise { @@ -44,7 +48,13 @@ export class OrganizationBillingService implements OrganizationBillingServiceAbs this.setPaymentInformation(request, subscription.payment); - return await this.organizationApiService.create(request); + const response = await this.organizationApiService.create(request); + + await this.apiService.refreshIdentityToken(); + + await this.syncService.fullSync(true); + + return response; } async startFree(subscription: SubscriptionInformation): Promise { @@ -58,7 +68,13 @@ export class OrganizationBillingService implements OrganizationBillingServiceAbs this.setPlanInformation(request, subscription.plan); - return await this.organizationApiService.create(request); + const response = await this.organizationApiService.create(request); + + await this.apiService.refreshIdentityToken(); + + await this.syncService.fullSync(true); + + return response; } private async makeOrganizationKeys(): Promise {