1
0
mirror of https://github.com/bitwarden/browser.git synced 2024-12-22 16:29:09 +01:00

Added create-client-organization.component (#8767)

This commit is contained in:
Alex Morask 2024-04-18 08:36:38 -04:00 committed by GitHub
parent cbaf3462c1
commit 1e0ad09757
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 409 additions and 26 deletions

View File

@ -7958,5 +7958,26 @@
}, },
"errorAssigningTargetFolder": { "errorAssigningTargetFolder": {
"message": "Error assigning target folder." "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"
} }
} }

View File

@ -69,7 +69,7 @@ const routes: Routes = [
{ {
path: "manage-client-organizations", path: "manage-client-organizations",
component: ManageClientOrganizationsComponent, component: ManageClientOrganizationsComponent,
data: { titleId: "manage-client-organizations" }, data: { titleId: "clients" },
}, },
{ {
path: "manage", path: "manage",

View File

@ -4,13 +4,16 @@ import { FormsModule } from "@angular/forms";
import { JslibModule } from "@bitwarden/angular/jslib.module"; import { JslibModule } from "@bitwarden/angular/jslib.module";
import { SearchModule } from "@bitwarden/components"; 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 { OrganizationPlansComponent, TaxInfoComponent } from "@bitwarden/web-vault/app/billing";
import { PaymentMethodWarningsModule } from "@bitwarden/web-vault/app/billing/shared"; import { PaymentMethodWarningsModule } from "@bitwarden/web-vault/app/billing/shared";
import { OssModule } from "@bitwarden/web-vault/app/oss.module"; import { OssModule } from "@bitwarden/web-vault/app/oss.module";
import { DangerZoneComponent } from "../../../../../../apps/web/src/app/auth/settings/account/danger-zone.component"; import {
import { ManageClientOrganizationSubscriptionComponent } from "../../billing/providers/clients/manage-client-organization-subscription.component"; CreateClientOrganizationComponent,
import { ManageClientOrganizationsComponent } from "../../billing/providers/clients/manage-client-organizations.component"; ManageClientOrganizationSubscriptionComponent,
ManageClientOrganizationsComponent,
} from "../../billing/providers/clients";
import { AddOrganizationComponent } from "./clients/add-organization.component"; import { AddOrganizationComponent } from "./clients/add-organization.component";
import { ClientsComponent } from "./clients/clients.component"; import { ClientsComponent } from "./clients/clients.component";
@ -56,6 +59,7 @@ import { SetupComponent } from "./setup/setup.component";
SetupComponent, SetupComponent,
SetupProviderComponent, SetupProviderComponent,
UserAddEditComponent, UserAddEditComponent,
CreateClientOrganizationComponent,
ManageClientOrganizationsComponent, ManageClientOrganizationsComponent,
ManageClientOrganizationSubscriptionComponent, ManageClientOrganizationSubscriptionComponent,
], ],

View File

@ -1,8 +1,15 @@
import { Injectable } from "@angular/core"; import { Injectable } from "@angular/core";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; 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 { 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 { 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"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
@Injectable() @Injectable()
@ -11,6 +18,9 @@ export class WebProviderService {
private cryptoService: CryptoService, private cryptoService: CryptoService,
private syncService: SyncService, private syncService: SyncService,
private apiService: ApiService, private apiService: ApiService,
private i18nService: I18nService,
private encryptService: EncryptService,
private billingApiService: BillingApiServiceAbstraction,
) {} ) {}
async addOrganizationToProvider(providerId: string, organizationId: string) { async addOrganizationToProvider(providerId: string, organizationId: string) {
@ -28,6 +38,46 @@ export class WebProviderService {
return response; return response;
} }
async createClientOrganization(
providerId: string,
name: string,
ownerEmail: string,
planType: PlanType,
seats: number,
): Promise<void> {
const organizationKey = (await this.cryptoService.makeOrgKey<OrgKey>())[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<any> { async detachOrganization(providerId: string, organizationId: string): Promise<any> {
await this.apiService.deleteProviderOrganization(providerId, organizationId); await this.apiService.deleteProviderOrganization(providerId, organizationId);
await this.syncService.fullSync(true); await this.syncService.fullSync(true);

View File

@ -0,0 +1,69 @@
<form [formGroup]="formGroup" [bitSubmit]="submit">
<bit-dialog dialogSize="large">
<span bitDialogTitle class="tw-font-semibold">
{{ "newClientOrganization" | i18n }}
</span>
<div bitDialogContent>
<p>{{ "createNewClientToManageAsProvider" | i18n }}</p>
<div class="tw-mb-3">
<span class="tw-text-lg tw-pr-1">{{ "selectAPlan" | i18n }}</span>
<span bitBadge variant="success">{{ "thirtyFivePercentDiscount" | i18n }}</span>
</div>
<ng-container>
<div class="tw-grid tw-grid-flow-col tw-grid-cols-2 tw-gap-4 tw-mb-4">
<div
*ngFor="let planCard of planCards"
[ngClass]="getPlanCardContainerClasses(planCard.selected)"
(click)="selectPlan(planCard.name)"
>
<div class="tw-relative">
<div
*ngIf="planCard.selected"
class="tw-bg-primary-600 tw-text-center !tw-text-contrast tw-text-sm tw-font-bold tw-py-1 group-hover:tw-bg-primary-700"
>
{{ "selected" | i18n }}
</div>
<div class="tw-p-5" [ngClass]="{ 'tw-pt-12': !planCard.selected }">
<h3 class="tw-text-2xl tw-font-bold tw-uppercase">{{ planCard.name }}</h3>
<span class="tw-text-2xl tw-font-semibold">{{
planCard.cost | currency: "$"
}}</span>
<span class="tw-text-sm tw-font-bold">/{{ "monthPerMember" | i18n }}</span>
</div>
</div>
</div>
</div>
</ng-container>
<div class="tw-grid tw-grid-flow-col tw-grid-cols-2 tw-gap-4">
<bit-form-field>
<bit-label>
{{ "organizationName" | i18n }}
</bit-label>
<input type="text" bitInput formControlName="organizationName" />
</bit-form-field>
<bit-form-field>
<bit-label>
{{ "clientOwnerEmail" | i18n }}
</bit-label>
<input type="text" bitInput formControlName="clientOwnerEmail" />
</bit-form-field>
</div>
<div class="tw-grid tw-grid-flow-col tw-grid-cols-2 tw-gap-4">
<bit-form-field>
<bit-label>
{{ "seats" | i18n }}
</bit-label>
<input type="text" bitInput formControlName="seats" />
</bit-form-field>
</div>
</div>
<ng-container bitDialogFooter>
<button bitButton bitFormButton buttonType="primary" type="submit">
{{ "addOrganization" | i18n }}
</button>
<button bitButton buttonType="secondary" type="button" [bitDialogClose]="ResultType.Closed">
{{ "close" | i18n }}
</button>
</ng-container>
</bit-dialog>
</form>

View File

@ -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<CreateClientOrganizationParams>,
) =>
dialogService.open<CreateClientOrganizationResultType, CreateClientOrganizationParams>(
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<CreateClientOrganizationResultType>,
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<void> {
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);
};
}

View File

@ -0,0 +1,3 @@
export * from "./create-client-organization.component";
export * from "./manage-client-organizations.component";
export * from "./manage-client-organization-subscription.component";

View File

@ -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 { 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 { 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 { Plans } from "@bitwarden/common/billing/models/response/provider-subscription-response";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
@ -45,7 +45,7 @@ export class ManageClientOrganizationSubscriptionComponent implements OnInit {
async ngOnInit() { async ngOnInit() {
try { 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); this.AdditionalSeatPurchased = this.getPurchasedSeatsByPlan(this.planName, response.plans);
const seatMinimum = this.getProviderSeatMinimumByPlan(this.planName, response.plans); const seatMinimum = this.getProviderSeatMinimumByPlan(this.planName, response.plans);
const assignedByPlan = this.getAssignedByPlan(this.planName, response.plans); const assignedByPlan = this.getAssignedByPlan(this.planName, response.plans);
@ -69,10 +69,10 @@ export class ManageClientOrganizationSubscriptionComponent implements OnInit {
return; return;
} }
const request = new ProviderSubscriptionUpdateRequest(); const request = new UpdateClientOrganizationRequest();
request.assignedSeats = assignedSeats; request.assignedSeats = assignedSeats;
await this.billingApiService.putProviderClientSubscriptions( await this.billingApiService.updateClientOrganization(
this.providerId, this.providerId,
this.providerOrganizationId, this.providerOrganizationId,
request, request,

View File

@ -1,6 +1,12 @@
<app-header> <app-header>
<bit-search [placeholder]="'search' | i18n" [(ngModel)]="searchText"></bit-search> <bit-search [placeholder]="'search' | i18n" [(ngModel)]="searchText"></bit-search>
<a bitButton routerLink="create" *ngIf="manageOrganizations" buttonType="primary"> <a
type="button"
bitButton
*ngIf="manageOrganizations"
buttonType="primary"
(click)="createClientOrganization()"
>
<i class="bwi bwi-plus bwi-fw" aria-hidden="true"></i> <i class="bwi bwi-plus bwi-fw" aria-hidden="true"></i>
{{ "addNewOrganization" | i18n }} {{ "addNewOrganization" | i18n }}
</a> </a>

View File

@ -1,7 +1,7 @@
import { SelectionModel } from "@angular/cdk/collections"; import { SelectionModel } from "@angular/cdk/collections";
import { Component, OnDestroy, OnInit } from "@angular/core"; import { Component, OnDestroy, OnInit } from "@angular/core";
import { ActivatedRoute } from "@angular/router"; 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 { first, switchMap, takeUntil } from "rxjs/operators";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; 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 { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service";
import { ProviderUserType } from "@bitwarden/common/admin-console/enums"; import { ProviderUserType } from "@bitwarden/common/admin-console/enums";
import { ProviderOrganizationOrganizationDetailsResponse } from "@bitwarden/common/admin-console/models/response/provider/provider-organization.response"; 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 { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.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 { 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"; import { ManageClientOrganizationSubscriptionComponent } from "./manage-client-organization-subscription.component";
@Component({ @Component({
@ -52,6 +58,7 @@ export class ManageClientOrganizationsComponent implements OnInit, OnDestroy {
private pagedClientsCount = 0; private pagedClientsCount = 0;
selection = new SelectionModel<string>(true, []); selection = new SelectionModel<string>(true, []);
protected dataSource = new TableDataSource<ProviderOrganizationOrganizationDetailsResponse>(); protected dataSource = new TableDataSource<ProviderOrganizationOrganizationDetailsResponse>();
protected plans: PlanResponse[];
constructor( constructor(
private route: ActivatedRoute, private route: ActivatedRoute,
@ -63,6 +70,7 @@ export class ManageClientOrganizationsComponent implements OnInit, OnDestroy {
private validationService: ValidationService, private validationService: ValidationService,
private webProviderService: WebProviderService, private webProviderService: WebProviderService,
private dialogService: DialogService, private dialogService: DialogService,
private billingApiService: BillingApiService,
) {} ) {}
async ngOnInit() { async ngOnInit() {
@ -94,12 +102,16 @@ export class ManageClientOrganizationsComponent implements OnInit, OnDestroy {
} }
async load() { async load() {
const response = await this.apiService.getProviderClients(this.providerId); const clientsResponse = await this.apiService.getProviderClients(this.providerId);
this.clients = response.data != null && response.data.length > 0 ? response.data : []; this.clients =
clientsResponse.data != null && clientsResponse.data.length > 0 ? clientsResponse.data : [];
this.dataSource.data = this.clients; this.dataSource.data = this.clients;
this.manageOrganizations = this.manageOrganizations =
(await this.providerService.get(this.providerId)).type === ProviderUserType.ProviderAdmin; (await this.providerService.get(this.providerId)).type === ProviderUserType.ProviderAdmin;
const plansResponse = await this.billingApiService.getPlans();
this.plans = plansResponse.data;
this.loading = false; this.loading = false;
} }
@ -177,4 +189,21 @@ export class ManageClientOrganizationsComponent implements OnInit, OnDestroy {
} }
this.actionPromise = null; 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();
};
} }

View File

@ -1051,10 +1051,12 @@ const safeProviders: SafeProvider[] = [
provide: OrganizationBillingServiceAbstraction, provide: OrganizationBillingServiceAbstraction,
useClass: OrganizationBillingService, useClass: OrganizationBillingService,
deps: [ deps: [
ApiServiceAbstraction,
CryptoServiceAbstraction, CryptoServiceAbstraction,
EncryptService, EncryptService,
I18nServiceAbstraction, I18nServiceAbstraction,
OrganizationApiServiceAbstraction, OrganizationApiServiceAbstraction,
SyncServiceAbstraction,
], ],
}), }),
safeProvider({ safeProvider({

View File

@ -1,6 +1,9 @@
import { SubscriptionCancellationRequest } from "../../billing/models/request/subscription-cancellation.request"; import { SubscriptionCancellationRequest } from "../../billing/models/request/subscription-cancellation.request";
import { OrganizationBillingStatusResponse } from "../../billing/models/response/organization-billing-status.response"; 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"; import { ProviderSubscriptionResponse } from "../models/response/provider-subscription-response";
export abstract class BillingApiServiceAbstraction { export abstract class BillingApiServiceAbstraction {
@ -9,11 +12,16 @@ export abstract class BillingApiServiceAbstraction {
request: SubscriptionCancellationRequest, request: SubscriptionCancellationRequest,
) => Promise<void>; ) => Promise<void>;
cancelPremiumUserSubscription: (request: SubscriptionCancellationRequest) => Promise<void>; cancelPremiumUserSubscription: (request: SubscriptionCancellationRequest) => Promise<void>;
createClientOrganization: (
providerId: string,
request: CreateClientOrganizationRequest,
) => Promise<void>;
getBillingStatus: (id: string) => Promise<OrganizationBillingStatusResponse>; getBillingStatus: (id: string) => Promise<OrganizationBillingStatusResponse>;
getProviderClientSubscriptions: (providerId: string) => Promise<ProviderSubscriptionResponse>; getPlans: () => Promise<ListResponse<PlanResponse>>;
putProviderClientSubscriptions: ( getProviderSubscription: (providerId: string) => Promise<ProviderSubscriptionResponse>;
updateClientOrganization: (
providerId: string, providerId: string,
organizationId: string, organizationId: string,
request: ProviderSubscriptionUpdateRequest, request: UpdateClientOrganizationRequest,
) => Promise<any>; ) => Promise<any>;
} }

View File

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

View File

@ -1,3 +0,0 @@
export class ProviderSubscriptionUpdateRequest {
assignedSeats: number;
}

View File

@ -0,0 +1,3 @@
export class UpdateClientOrganizationRequest {
assignedSeats: number;
}

View File

@ -2,7 +2,10 @@ import { ApiService } from "../../abstractions/api.service";
import { BillingApiServiceAbstraction } from "../../billing/abstractions/billilng-api.service.abstraction"; import { BillingApiServiceAbstraction } from "../../billing/abstractions/billilng-api.service.abstraction";
import { SubscriptionCancellationRequest } from "../../billing/models/request/subscription-cancellation.request"; import { SubscriptionCancellationRequest } from "../../billing/models/request/subscription-cancellation.request";
import { OrganizationBillingStatusResponse } from "../../billing/models/response/organization-billing-status.response"; 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"; import { ProviderSubscriptionResponse } from "../models/response/provider-subscription-response";
export class BillingApiService implements BillingApiServiceAbstraction { export class BillingApiService implements BillingApiServiceAbstraction {
@ -25,6 +28,19 @@ export class BillingApiService implements BillingApiServiceAbstraction {
return this.apiService.send("POST", "/accounts/cancel", request, true, false); return this.apiService.send("POST", "/accounts/cancel", request, true, false);
} }
createClientOrganization(
providerId: string,
request: CreateClientOrganizationRequest,
): Promise<void> {
return this.apiService.send(
"POST",
"/providers/" + providerId + "/clients",
request,
true,
false,
);
}
async getBillingStatus(id: string): Promise<OrganizationBillingStatusResponse> { async getBillingStatus(id: string): Promise<OrganizationBillingStatusResponse> {
const r = await this.apiService.send( const r = await this.apiService.send(
"GET", "GET",
@ -37,7 +53,12 @@ export class BillingApiService implements BillingApiServiceAbstraction {
return new OrganizationBillingStatusResponse(r); return new OrganizationBillingStatusResponse(r);
} }
async getProviderClientSubscriptions(providerId: string): Promise<ProviderSubscriptionResponse> { async getPlans(): Promise<ListResponse<PlanResponse>> {
const r = await this.apiService.send("GET", "/plans", null, false, true);
return new ListResponse(r, PlanResponse);
}
async getProviderSubscription(providerId: string): Promise<ProviderSubscriptionResponse> {
const r = await this.apiService.send( const r = await this.apiService.send(
"GET", "GET",
"/providers/" + providerId + "/billing/subscription", "/providers/" + providerId + "/billing/subscription",
@ -48,14 +69,14 @@ export class BillingApiService implements BillingApiServiceAbstraction {
return new ProviderSubscriptionResponse(r); return new ProviderSubscriptionResponse(r);
} }
async putProviderClientSubscriptions( async updateClientOrganization(
providerId: string, providerId: string,
organizationId: string, organizationId: string,
request: ProviderSubscriptionUpdateRequest, request: UpdateClientOrganizationRequest,
): Promise<any> { ): Promise<any> {
return await this.apiService.send( return await this.apiService.send(
"PUT", "PUT",
"/providers/" + providerId + "/organizations/" + organizationId, "/providers/" + providerId + "/clients/" + organizationId,
request, request,
true, true,
false, false,

View File

@ -1,3 +1,4 @@
import { ApiService } from "../../abstractions/api.service";
import { OrganizationApiServiceAbstraction as OrganizationApiService } from "../../admin-console/abstractions/organization/organization-api.service.abstraction"; import { OrganizationApiServiceAbstraction as OrganizationApiService } from "../../admin-console/abstractions/organization/organization-api.service.abstraction";
import { OrganizationCreateRequest } from "../../admin-console/models/request/organization-create.request"; import { OrganizationCreateRequest } from "../../admin-console/models/request/organization-create.request";
import { OrganizationKeysRequest } from "../../admin-console/models/request/organization-keys.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 { I18nService } from "../../platform/abstractions/i18n.service";
import { EncString } from "../../platform/models/domain/enc-string"; import { EncString } from "../../platform/models/domain/enc-string";
import { OrgKey } from "../../types/key"; import { OrgKey } from "../../types/key";
import { SyncService } from "../../vault/abstractions/sync/sync.service.abstraction";
import { import {
OrganizationBillingServiceAbstraction, OrganizationBillingServiceAbstraction,
OrganizationInformation, OrganizationInformation,
@ -25,10 +27,12 @@ interface OrganizationKeys {
export class OrganizationBillingService implements OrganizationBillingServiceAbstraction { export class OrganizationBillingService implements OrganizationBillingServiceAbstraction {
constructor( constructor(
private apiService: ApiService,
private cryptoService: CryptoService, private cryptoService: CryptoService,
private encryptService: EncryptService, private encryptService: EncryptService,
private i18nService: I18nService, private i18nService: I18nService,
private organizationApiService: OrganizationApiService, private organizationApiService: OrganizationApiService,
private syncService: SyncService,
) {} ) {}
async purchaseSubscription(subscription: SubscriptionInformation): Promise<OrganizationResponse> { async purchaseSubscription(subscription: SubscriptionInformation): Promise<OrganizationResponse> {
@ -44,7 +48,13 @@ export class OrganizationBillingService implements OrganizationBillingServiceAbs
this.setPaymentInformation(request, subscription.payment); 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<OrganizationResponse> { async startFree(subscription: SubscriptionInformation): Promise<OrganizationResponse> {
@ -58,7 +68,13 @@ export class OrganizationBillingService implements OrganizationBillingServiceAbs
this.setPlanInformation(request, subscription.plan); 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<OrganizationKeys> { private async makeOrganizationKeys(): Promise<OrganizationKeys> {