mirror of
https://github.com/bitwarden/browser
synced 2026-02-11 22:13:32 +00:00
changes to restart a unpaid or cancel subscription
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
<form [formGroup]="formGroup" [bitSubmit]="submit">
|
||||
<bit-dialog dialogSize="large" [loading]="loading">
|
||||
<span bitDialogTitle class="tw-font-semibold">
|
||||
{{ "upgradeFreeOrganization" | i18n: currentPlanName }}
|
||||
{{ dialogHeaderName }}
|
||||
</span>
|
||||
<div bitDialogContent>
|
||||
<p>{{ "upgradePlans" | i18n }}</p>
|
||||
@@ -330,9 +330,15 @@
|
||||
<br />
|
||||
</ng-container>
|
||||
<!-- Payment info -->
|
||||
<ng-container *ngIf="formGroup.value.productTier !== productTypes.Free">
|
||||
<ng-container
|
||||
*ngIf="formGroup.value.productTier !== productTypes.Free || isSubscriptionCanceled"
|
||||
>
|
||||
<h2 bitTypography="h4">{{ "paymentMethod" | i18n }}</h2>
|
||||
<p *ngIf="!showPayment && (paymentSource || billing?.paymentSource)">
|
||||
<p
|
||||
*ngIf="
|
||||
!showPayment && (paymentSource || billing?.paymentSource) && !isSubscriptionCanceled
|
||||
"
|
||||
>
|
||||
<i class="bwi bwi-fw" [ngClass]="paymentSourceClasses"></i>
|
||||
{{
|
||||
deprecateStripeSourcesAPI
|
||||
@@ -344,23 +350,11 @@
|
||||
</span>
|
||||
<a></a>
|
||||
</p>
|
||||
<app-payment
|
||||
*ngIf="
|
||||
(upgradeRequiresPaymentMethod || showPayment || isPaymentSourceEmpty()) &&
|
||||
!deprecateStripeSourcesAPI
|
||||
"
|
||||
[hideCredit]="true"
|
||||
></app-payment>
|
||||
<app-payment-v2
|
||||
*ngIf="
|
||||
(upgradeRequiresPaymentMethod || showPayment || isPaymentSourceEmpty()) &&
|
||||
deprecateStripeSourcesAPI
|
||||
"
|
||||
[showAccountCredit]="false"
|
||||
>
|
||||
<app-payment *ngIf="shouldShowPaymentOptions()" [hideCredit]="true"></app-payment>
|
||||
<app-payment-v2 *ngIf="shouldShowDeprecatedPaymentOptions()" [showAccountCredit]="false">
|
||||
</app-payment-v2>
|
||||
<app-tax-info
|
||||
*ngIf="showPayment || upgradeRequiresPaymentMethod || isPaymentSourceEmpty()"
|
||||
*ngIf="canShowPaymentDetails()"
|
||||
(onCountryChanged)="changedCountry()"
|
||||
></app-tax-info>
|
||||
<div id="price" class="tw-mt-4">
|
||||
|
||||
@@ -23,7 +23,14 @@ import { PolicyType } from "@bitwarden/common/admin-console/enums";
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import { OrganizationKeysRequest } from "@bitwarden/common/admin-console/models/request/organization-keys.request";
|
||||
import { OrganizationUpgradeRequest } from "@bitwarden/common/admin-console/models/request/organization-upgrade.request";
|
||||
import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions";
|
||||
import {
|
||||
BillingApiServiceAbstraction,
|
||||
BillingInformation,
|
||||
OrganizationInformation,
|
||||
PaymentInformation,
|
||||
PlanInformation,
|
||||
OrganizationBillingServiceAbstraction as OrganizationBillingService,
|
||||
} from "@bitwarden/common/billing/abstractions";
|
||||
import {
|
||||
PaymentMethodType,
|
||||
PlanInterval,
|
||||
@@ -44,10 +51,10 @@ import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.serv
|
||||
import { DialogService, ToastService } from "@bitwarden/components";
|
||||
import { KeyService } from "@bitwarden/key-management";
|
||||
|
||||
import { BillingSharedModule } from "../shared/billing-shared.module";
|
||||
import { PaymentV2Component } from "../shared/payment/payment-v2.component";
|
||||
import { PaymentComponent } from "../shared/payment/payment.component";
|
||||
import { TaxInfoComponent } from "../shared/tax-info.component";
|
||||
|
||||
type ChangePlanDialogParams = {
|
||||
organizationId: string;
|
||||
subscription: OrganizationSubscriptionResponse;
|
||||
@@ -85,6 +92,8 @@ interface OnSuccessArgs {
|
||||
|
||||
@Component({
|
||||
templateUrl: "./change-plan-dialog.component.html",
|
||||
standalone: true,
|
||||
imports: [BillingSharedModule],
|
||||
})
|
||||
export class ChangePlanDialogComponent implements OnInit, OnDestroy {
|
||||
@ViewChild(PaymentComponent) paymentComponent: PaymentComponent;
|
||||
@@ -159,7 +168,7 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
|
||||
organization: Organization;
|
||||
sub: OrganizationSubscriptionResponse;
|
||||
billing: BillingResponse;
|
||||
currentPlanName: string;
|
||||
dialogHeaderName: string;
|
||||
showPayment: boolean = false;
|
||||
totalOpened: boolean = false;
|
||||
currentPlan: PlanResponse;
|
||||
@@ -170,6 +179,7 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
|
||||
paymentSource?: PaymentSourceResponse;
|
||||
|
||||
deprecateStripeSourcesAPI: boolean;
|
||||
isSubscriptionCanceled: boolean = false;
|
||||
|
||||
private destroy$ = new Subject<void>();
|
||||
|
||||
@@ -189,6 +199,7 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
|
||||
private organizationApiService: OrganizationApiServiceAbstraction,
|
||||
private configService: ConfigService,
|
||||
private billingApiService: BillingApiServiceAbstraction,
|
||||
private organizationBillingService: OrganizationBillingService,
|
||||
) {}
|
||||
|
||||
async ngOnInit(): Promise<void> {
|
||||
@@ -197,10 +208,10 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
|
||||
);
|
||||
|
||||
if (this.dialogParams.organizationId) {
|
||||
this.currentPlanName = this.resolvePlanName(this.dialogParams.productTierType);
|
||||
this.sub =
|
||||
this.dialogParams.subscription ??
|
||||
(await this.organizationApiService.getSubscription(this.dialogParams.organizationId));
|
||||
this.dialogHeaderName = this.resolveHeaderName(this.sub);
|
||||
this.organizationId = this.dialogParams.organizationId;
|
||||
this.currentPlan = this.sub?.plan;
|
||||
this.selectedPlan = this.sub?.plan;
|
||||
@@ -269,9 +280,42 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
|
||||
this.loading = false;
|
||||
}
|
||||
|
||||
resolveHeaderName(subscription: OrganizationSubscriptionResponse): string {
|
||||
if (subscription.subscription != null) {
|
||||
this.isSubscriptionCanceled = subscription.subscription.cancelled;
|
||||
if (subscription.subscription.cancelled) {
|
||||
return this.i18nService.t("restartSubscription");
|
||||
}
|
||||
}
|
||||
|
||||
return this.i18nService.t(
|
||||
"upgradeFreeOrganization",
|
||||
this.resolvePlanName(this.dialogParams.productTierType),
|
||||
);
|
||||
}
|
||||
|
||||
shouldShowPaymentOptions(): boolean {
|
||||
return this.canShowPaymentDetails() && !this.deprecateStripeSourcesAPI;
|
||||
}
|
||||
|
||||
shouldShowDeprecatedPaymentOptions(): boolean {
|
||||
return this.canShowPaymentDetails() && this.deprecateStripeSourcesAPI;
|
||||
}
|
||||
|
||||
canShowPaymentDetails(): boolean {
|
||||
return (
|
||||
this.upgradeRequiresPaymentMethod ||
|
||||
this.showPayment ||
|
||||
this.isPaymentSourceEmpty() ||
|
||||
this.isSubscriptionCanceled
|
||||
);
|
||||
}
|
||||
|
||||
setInitialPlanSelection() {
|
||||
this.focusedIndex = this.selectableProducts.length - 1;
|
||||
this.selectPlan(this.getPlanByType(ProductTierType.Enterprise));
|
||||
if (!this.isSubscriptionCanceled) {
|
||||
this.focusedIndex = this.selectableProducts.length - 1;
|
||||
this.selectPlan(this.getPlanByType(ProductTierType.Enterprise));
|
||||
}
|
||||
}
|
||||
|
||||
getPlanByType(productTier: ProductTierType) {
|
||||
@@ -376,6 +420,19 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
|
||||
];
|
||||
}
|
||||
case PlanCardState.Disabled: {
|
||||
if (this.isSubscriptionCanceled) {
|
||||
return [
|
||||
"tw-cursor-not-allowed",
|
||||
"tw-bg-secondary-100",
|
||||
"tw-font-normal",
|
||||
"tw-bg-blur",
|
||||
"tw-text-muted",
|
||||
"tw-block",
|
||||
"tw-rounded",
|
||||
"tw-w-80",
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
"tw-cursor-not-allowed",
|
||||
"tw-bg-secondary-100",
|
||||
@@ -397,7 +454,7 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
|
||||
return;
|
||||
}
|
||||
|
||||
if (plan === this.currentPlan) {
|
||||
if (plan === this.currentPlan && !this.isSubscriptionCanceled) {
|
||||
return;
|
||||
}
|
||||
this.selectedPlan = plan;
|
||||
@@ -428,6 +485,11 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
get selectableProducts() {
|
||||
if (this.isSubscriptionCanceled) {
|
||||
// Return only the current plan if the subscription is canceled
|
||||
return [this.currentPlan];
|
||||
}
|
||||
|
||||
if (this.acceptingSponsorship) {
|
||||
const familyPlan = this.passwordManagerPlans.find(
|
||||
(plan) => plan.type === PlanType.FamiliesAnnually,
|
||||
@@ -675,11 +737,19 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
|
||||
|
||||
const doSubmit = async (): Promise<string> => {
|
||||
let orgId: string = null;
|
||||
orgId = await this.updateOrganization();
|
||||
if (this.isSubscriptionCanceled) {
|
||||
await this.restartSubscription();
|
||||
orgId = this.organizationId;
|
||||
} else {
|
||||
orgId = await this.updateOrganization();
|
||||
}
|
||||
|
||||
this.toastService.showToast({
|
||||
variant: "success",
|
||||
title: null,
|
||||
message: this.i18nService.t("organizationUpgraded"),
|
||||
message: this.isSubscriptionCanceled
|
||||
? this.i18nService.t("restartOrganizationSubscription")
|
||||
: this.i18nService.t("organizationUpgraded"),
|
||||
});
|
||||
|
||||
await this.apiService.refreshIdentityToken();
|
||||
@@ -709,6 +779,44 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
|
||||
this.dialogRef.close();
|
||||
};
|
||||
|
||||
private async restartSubscription() {
|
||||
const org = await this.organizationApiService.get(this.organizationId);
|
||||
const organization: OrganizationInformation = {
|
||||
name: org.name,
|
||||
billingEmail: org.billingEmail,
|
||||
};
|
||||
|
||||
const plan: PlanInformation = {
|
||||
type: this.selectedPlan.type,
|
||||
passwordManagerSeats: org.seats,
|
||||
};
|
||||
|
||||
if (org.useSecretsManager) {
|
||||
plan.subscribeToSecretsManager = true;
|
||||
plan.secretsManagerSeats = org.smSeats;
|
||||
}
|
||||
|
||||
let paymentMethod: [string, PaymentMethodType];
|
||||
|
||||
if (this.deprecateStripeSourcesAPI) {
|
||||
const { type, token } = await this.paymentV2Component.tokenize();
|
||||
paymentMethod = [token, type];
|
||||
} else {
|
||||
paymentMethod = await this.paymentComponent.createPaymentToken();
|
||||
}
|
||||
|
||||
const payment: PaymentInformation = {
|
||||
paymentMethod,
|
||||
billing: this.getBillingInformationFromTaxInfoComponent(),
|
||||
};
|
||||
|
||||
await this.organizationBillingService.restartSubscription(this.organization.id, {
|
||||
organization,
|
||||
plan,
|
||||
payment,
|
||||
});
|
||||
}
|
||||
|
||||
private async updateOrganization() {
|
||||
const request = new OrganizationUpgradeRequest();
|
||||
if (this.selectedPlan.productTier !== ProductTierType.Families) {
|
||||
@@ -774,6 +882,18 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
|
||||
return this.organizationId;
|
||||
}
|
||||
|
||||
private getBillingInformationFromTaxInfoComponent(): BillingInformation {
|
||||
return {
|
||||
country: this.taxComponent.country,
|
||||
postalCode: this.taxComponent.postalCode,
|
||||
taxId: this.taxComponent.taxId,
|
||||
addressLine1: this.taxComponent.line1,
|
||||
addressLine2: this.taxComponent.line2,
|
||||
city: this.taxComponent.city,
|
||||
state: this.taxComponent.state,
|
||||
};
|
||||
}
|
||||
|
||||
private billingSubLabelText(): string {
|
||||
const selectedPlan = this.selectedPlan;
|
||||
const price =
|
||||
|
||||
@@ -8,7 +8,6 @@ import { BillingSharedModule } from "../shared";
|
||||
import { AdjustSubscription } from "./adjust-subscription.component";
|
||||
import { BillingSyncApiKeyComponent } from "./billing-sync-api-key.component";
|
||||
import { BillingSyncKeyComponent } from "./billing-sync-key.component";
|
||||
import { ChangePlanDialogComponent } from "./change-plan-dialog.component";
|
||||
import { ChangePlanComponent } from "./change-plan.component";
|
||||
import { DownloadLicenceDialogComponent } from "./download-license.component";
|
||||
import { OrgBillingHistoryViewComponent } from "./organization-billing-history-view.component";
|
||||
@@ -44,7 +43,6 @@ import { SubscriptionStatusComponent } from "./subscription-status.component";
|
||||
SecretsManagerSubscribeStandaloneComponent,
|
||||
SubscriptionHiddenComponent,
|
||||
SubscriptionStatusComponent,
|
||||
ChangePlanDialogComponent,
|
||||
OrganizationPaymentMethodComponent,
|
||||
],
|
||||
})
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
// @ts-strict-ignore
|
||||
import { Injectable } from "@angular/core";
|
||||
import { Router } from "@angular/router";
|
||||
import { lastValueFrom } from "rxjs";
|
||||
|
||||
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/billing-api.service.abstraction";
|
||||
import { BillingSourceResponse } from "@bitwarden/common/billing/models/response/billing.response";
|
||||
@@ -13,6 +15,10 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic
|
||||
import { DialogService } from "@bitwarden/components";
|
||||
|
||||
import { FreeTrial } from "../../core/types/free-trial";
|
||||
import {
|
||||
ChangePlanDialogResultType,
|
||||
openChangePlanDialog,
|
||||
} from "../organizations/change-plan-dialog.component";
|
||||
|
||||
@Injectable({ providedIn: "root" })
|
||||
export class TrialFlowService {
|
||||
@@ -21,17 +27,18 @@ export class TrialFlowService {
|
||||
protected dialogService: DialogService,
|
||||
private router: Router,
|
||||
protected billingApiService: BillingApiServiceAbstraction,
|
||||
private organizationApiService: OrganizationApiServiceAbstraction,
|
||||
) {}
|
||||
checkForOrgsWithUpcomingPaymentIssues(
|
||||
organization: Organization,
|
||||
organizationSubscription: OrganizationSubscriptionResponse,
|
||||
paymentSource: BillingSourceResponse | PaymentSourceResponse,
|
||||
): FreeTrial {
|
||||
const trialEndDate = organizationSubscription?.subscription?.trialEndDate;
|
||||
const trialEndDate = organizationSubscription.subscription?.trialEndDate;
|
||||
const displayBanner =
|
||||
!paymentSource &&
|
||||
organization?.isOwner &&
|
||||
organizationSubscription?.subscription?.status === "trialing";
|
||||
organization.isOwner &&
|
||||
organizationSubscription?.subscription.status === "trialing";
|
||||
const trialRemainingDays = trialEndDate ? this.calculateTrialRemainingDays(trialEndDate) : 0;
|
||||
const freeTrialMessage = this.getFreeTrialMessage(trialRemainingDays);
|
||||
|
||||
@@ -64,20 +71,28 @@ export class TrialFlowService {
|
||||
|
||||
async handleUnpaidSubscriptionDialog(
|
||||
org: Organization,
|
||||
organizationBillingMetadata: OrganizationBillingMetadataResponse,
|
||||
billingMetadata: OrganizationBillingMetadataResponse,
|
||||
): Promise<void> {
|
||||
if (organizationBillingMetadata.isSubscriptionUnpaid) {
|
||||
const confirmed = await this.promptForPaymentNavigation(org);
|
||||
if (billingMetadata.isSubscriptionUnpaid || billingMetadata.isSubscriptionCanceled) {
|
||||
const confirmed = await this.promptForPaymentNavigation(
|
||||
org,
|
||||
billingMetadata.isSubscriptionCanceled,
|
||||
billingMetadata.isSubscriptionUnpaid,
|
||||
);
|
||||
if (confirmed) {
|
||||
await this.navigateToPaymentMethod(org?.id);
|
||||
await this.navigateToPaymentMethod(org.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async promptForPaymentNavigation(org: Organization): Promise<boolean> {
|
||||
if (!org?.isOwner) {
|
||||
private async promptForPaymentNavigation(
|
||||
org: Organization,
|
||||
isCanceled: boolean,
|
||||
isUnpaid: boolean,
|
||||
): Promise<boolean> {
|
||||
if (!org.isOwner && !org.isProviderUser) {
|
||||
await this.dialogService.openSimpleDialog({
|
||||
title: this.i18nService.t("suspendedOrganizationTitle", org?.name),
|
||||
title: this.i18nService.t("suspendedOrganizationTitle", org.name),
|
||||
content: { key: "suspendedUserOrgMessage" },
|
||||
type: "danger",
|
||||
acceptButtonText: this.i18nService.t("close"),
|
||||
@@ -85,13 +100,29 @@ export class TrialFlowService {
|
||||
});
|
||||
return false;
|
||||
}
|
||||
return await this.dialogService.openSimpleDialog({
|
||||
title: this.i18nService.t("suspendedOrganizationTitle", org?.name),
|
||||
content: { key: "suspendedOwnerOrgMessage" },
|
||||
type: "danger",
|
||||
acceptButtonText: this.i18nService.t("continue"),
|
||||
cancelButtonText: this.i18nService.t("close"),
|
||||
});
|
||||
if (org.hasProvider) {
|
||||
await this.dialogService.openSimpleDialog({
|
||||
title: this.i18nService.t("suspendedOrganizationTitle", org.name),
|
||||
content: { key: "suspendedManagedOrgMessage", placeholders: [org.providerName] },
|
||||
type: "danger",
|
||||
acceptButtonText: this.i18nService.t("close"),
|
||||
cancelButtonText: null,
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
if (org.isOwner && isUnpaid) {
|
||||
return await this.dialogService.openSimpleDialog({
|
||||
title: this.i18nService.t("suspendedOrganizationTitle", org.name),
|
||||
content: { key: "suspendedOwnerOrgMessage" },
|
||||
type: "danger",
|
||||
acceptButtonText: this.i18nService.t("continue"),
|
||||
cancelButtonText: this.i18nService.t("close"),
|
||||
});
|
||||
}
|
||||
if (org.isOwner && isCanceled) {
|
||||
await this.changePlan(org);
|
||||
}
|
||||
}
|
||||
|
||||
private async navigateToPaymentMethod(orgId: string) {
|
||||
@@ -99,4 +130,21 @@ export class TrialFlowService {
|
||||
state: { launchPaymentModalAutomatically: true },
|
||||
});
|
||||
}
|
||||
|
||||
private async changePlan(org: Organization) {
|
||||
const subscription = await this.organizationApiService.getSubscription(org.id);
|
||||
const reference = openChangePlanDialog(this.dialogService, {
|
||||
data: {
|
||||
organizationId: org.id,
|
||||
subscription: subscription,
|
||||
productTierType: org.productTierType,
|
||||
},
|
||||
});
|
||||
|
||||
const result = await lastValueFrom(reference.closed);
|
||||
|
||||
if (result === ChangePlanDialogResultType.Closed) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ import { firstValueFrom, Subject } from "rxjs";
|
||||
|
||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||
import { PolicyType } from "@bitwarden/common/admin-console/enums";
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/billing-api.service.abstraction";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
@@ -16,6 +15,7 @@ import { CipherType } from "@bitwarden/common/vault/enums";
|
||||
import { TreeNode } from "@bitwarden/common/vault/models/domain/tree-node";
|
||||
import { DialogService } from "@bitwarden/components";
|
||||
|
||||
import { TrialFlowService } from "../../../../billing/services/trial-flow.service";
|
||||
import { VaultFilterService } from "../services/abstractions/vault-filter.service";
|
||||
import {
|
||||
VaultFilterList,
|
||||
@@ -90,7 +90,7 @@ export class VaultFilterComponent implements OnInit, OnDestroy {
|
||||
|
||||
return "searchVault";
|
||||
}
|
||||
|
||||
private trialFlowService = inject(TrialFlowService);
|
||||
constructor(
|
||||
protected vaultFilterService: VaultFilterService,
|
||||
protected policyService: PolicyService,
|
||||
@@ -126,12 +126,7 @@ export class VaultFilterComponent implements OnInit, OnDestroy {
|
||||
this.i18nService.t("disabledOrganizationFilterError"),
|
||||
);
|
||||
const metadata = await this.billingApiService.getOrganizationBillingMetadata(orgNode.node.id);
|
||||
if (metadata.isSubscriptionUnpaid) {
|
||||
const confirmed = await this.promptForPaymentNavigation(orgNode.node);
|
||||
if (confirmed) {
|
||||
await this.navigateToPaymentMethod(orgNode.node.id);
|
||||
}
|
||||
}
|
||||
await this.trialFlowService.handleUnpaidSubscriptionDialog(orgNode.node, metadata);
|
||||
return;
|
||||
}
|
||||
const filter = this.activeFilter;
|
||||
@@ -144,32 +139,6 @@ export class VaultFilterComponent implements OnInit, OnDestroy {
|
||||
await this.vaultFilterService.expandOrgFilter();
|
||||
};
|
||||
|
||||
private async promptForPaymentNavigation(org: Organization): Promise<boolean> {
|
||||
if (!org?.isOwner) {
|
||||
await this.dialogService.openSimpleDialog({
|
||||
title: this.i18nService.t("suspendedOrganizationTitle", org?.name),
|
||||
content: { key: "suspendedUserOrgMessage" },
|
||||
type: "danger",
|
||||
acceptButtonText: this.i18nService.t("close"),
|
||||
cancelButtonText: null,
|
||||
});
|
||||
return false;
|
||||
}
|
||||
return await this.dialogService.openSimpleDialog({
|
||||
title: this.i18nService.t("suspendedOrganizationTitle", org?.name),
|
||||
content: { key: "suspendedOwnerOrgMessage" },
|
||||
type: "danger",
|
||||
acceptButtonText: this.i18nService.t("continue"),
|
||||
cancelButtonText: this.i18nService.t("close"),
|
||||
});
|
||||
}
|
||||
|
||||
private async navigateToPaymentMethod(orgId: string) {
|
||||
await this.router.navigate(["organizations", `${orgId}`, "billing", "payment-method"], {
|
||||
state: { launchPaymentModalAutomatically: true },
|
||||
});
|
||||
}
|
||||
|
||||
applyTypeFilter = async (filterNode: TreeNode<CipherTypeFilter>): Promise<void> => {
|
||||
const filter = this.activeFilter;
|
||||
filter.resetFilter();
|
||||
|
||||
@@ -3158,6 +3158,9 @@
|
||||
"organizationUpgraded": {
|
||||
"message": "Organization upgraded"
|
||||
},
|
||||
"restartOrganizationSubscription": {
|
||||
"message": "Organization subscription restarted"
|
||||
},
|
||||
"leave": {
|
||||
"message": "Leave"
|
||||
},
|
||||
@@ -9590,6 +9593,9 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"restartSubscription": {
|
||||
"message": "Restart your subscription"
|
||||
},
|
||||
"includeSsoAuthenticationMessage": {
|
||||
"message": "SSO Authentication"
|
||||
},
|
||||
@@ -9876,6 +9882,15 @@
|
||||
"suspendedUserOrgMessage": {
|
||||
"message": "Contact your organization owner for assistance."
|
||||
},
|
||||
"suspendedManagedOrgMessage": {
|
||||
"message": "Contact $PROVIDER$ for assistance.",
|
||||
"placeholders": {
|
||||
"provider": {
|
||||
"content": "$1",
|
||||
"example": "Acme c"
|
||||
}
|
||||
}
|
||||
},
|
||||
"suspendedOwnerOrgMessage": {
|
||||
"message": "To regain access to your organization, add a payment method."
|
||||
},
|
||||
|
||||
@@ -8,6 +8,7 @@ import { VerifyBankAccountRequest } from "@bitwarden/common/billing/models/reque
|
||||
import { InvoicesResponse } from "@bitwarden/common/billing/models/response/invoices.response";
|
||||
import { PaymentMethodResponse } from "@bitwarden/common/billing/models/response/payment-method.response";
|
||||
|
||||
import { OrganizationCreateRequest } from "../../admin-console/models/request/organization-create.request";
|
||||
import { SubscriptionCancellationRequest } from "../../billing/models/request/subscription-cancellation.request";
|
||||
import { OrganizationBillingMetadataResponse } from "../../billing/models/response/organization-billing-metadata.response";
|
||||
import { PlanResponse } from "../../billing/models/response/plan.response";
|
||||
@@ -74,4 +75,8 @@ export abstract class BillingApiServiceAbstraction {
|
||||
organizationId: string,
|
||||
request: VerifyBankAccountRequest,
|
||||
) => Promise<void>;
|
||||
restartSubscription: (
|
||||
organizationId: string,
|
||||
request: OrganizationCreateRequest,
|
||||
) => Promise<void>;
|
||||
}
|
||||
|
||||
@@ -57,4 +57,9 @@ export abstract class OrganizationBillingServiceAbstraction {
|
||||
) => Promise<OrganizationResponse>;
|
||||
|
||||
startFree: (subscription: SubscriptionInformation) => Promise<OrganizationResponse>;
|
||||
|
||||
restartSubscription: (
|
||||
organizationId: string,
|
||||
subscription: SubscriptionInformation,
|
||||
) => Promise<void>;
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ export class OrganizationBillingMetadataResponse extends BaseResponse {
|
||||
isOnSecretsManagerStandalone: boolean;
|
||||
isSubscriptionUnpaid: boolean;
|
||||
hasSubscription: boolean;
|
||||
isSubscriptionCanceled: boolean;
|
||||
|
||||
constructor(response: any) {
|
||||
super(response);
|
||||
@@ -14,5 +15,6 @@ export class OrganizationBillingMetadataResponse extends BaseResponse {
|
||||
this.isOnSecretsManagerStandalone = this.getResponseProperty("IsOnSecretsManagerStandalone");
|
||||
this.isSubscriptionUnpaid = this.getResponseProperty("IsSubscriptionUnpaid");
|
||||
this.hasSubscription = this.getResponseProperty("HasSubscription");
|
||||
this.isSubscriptionCanceled = this.getResponseProperty("IsSubscriptionCanceled");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service"
|
||||
import { ToastService } from "@bitwarden/components";
|
||||
|
||||
import { ApiService } from "../../abstractions/api.service";
|
||||
import { OrganizationCreateRequest } from "../../admin-console/models/request/organization-create.request";
|
||||
import { BillingApiServiceAbstraction } from "../../billing/abstractions";
|
||||
import { PaymentMethodType } from "../../billing/enums";
|
||||
import { ExpandedTaxInfoUpdateRequest } from "../../billing/models/request/expanded-tax-info-update.request";
|
||||
@@ -214,6 +215,19 @@ export class BillingApiService implements BillingApiServiceAbstraction {
|
||||
);
|
||||
}
|
||||
|
||||
async restartSubscription(
|
||||
organizationId: string,
|
||||
request: OrganizationCreateRequest,
|
||||
): Promise<void> {
|
||||
return await this.apiService.send(
|
||||
"POST",
|
||||
"/organizations/" + organizationId + "/billing/restart-subscription",
|
||||
request,
|
||||
true,
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
private async execute(request: () => Promise<any>): Promise<any> {
|
||||
try {
|
||||
return await request();
|
||||
|
||||
@@ -127,6 +127,25 @@ export class OrganizationBillingService implements OrganizationBillingServiceAbs
|
||||
return response;
|
||||
}
|
||||
|
||||
async restartSubscription(
|
||||
organizationId: string,
|
||||
subscription: SubscriptionInformation,
|
||||
): Promise<void> {
|
||||
const request = new OrganizationCreateRequest();
|
||||
|
||||
const organizationKeys = await this.makeOrganizationKeys();
|
||||
|
||||
this.setOrganizationKeys(request, organizationKeys);
|
||||
|
||||
this.setOrganizationInformation(request, subscription.organization);
|
||||
|
||||
this.setPlanInformation(request, subscription.plan);
|
||||
|
||||
this.setPaymentInformation(request, subscription.payment);
|
||||
|
||||
await this.billingApiService.restartSubscription(organizationId, request);
|
||||
}
|
||||
|
||||
private async makeOrganizationKeys(): Promise<OrganizationKeys> {
|
||||
const [encryptedKey, key] = await this.keyService.makeOrgKey<OrgKey>();
|
||||
const [publicKey, encryptedPrivateKey] = await this.keyService.makeKeyPair(key);
|
||||
|
||||
Reference in New Issue
Block a user