diff --git a/apps/web/src/app/billing/organizations/change-plan-dialog.component.ts b/apps/web/src/app/billing/organizations/change-plan-dialog.component.ts
index 572b7979515..5ea90a7b1c8 100644
--- a/apps/web/src/app/billing/organizations/change-plan-dialog.component.ts
+++ b/apps/web/src/app/billing/organizations/change-plan-dialog.component.ts
@@ -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
();
@@ -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 {
@@ -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 => {
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 =
diff --git a/apps/web/src/app/billing/organizations/organization-billing.module.ts b/apps/web/src/app/billing/organizations/organization-billing.module.ts
index b25cda662f2..48ac613711d 100644
--- a/apps/web/src/app/billing/organizations/organization-billing.module.ts
+++ b/apps/web/src/app/billing/organizations/organization-billing.module.ts
@@ -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,
],
})
diff --git a/apps/web/src/app/billing/services/trial-flow.service.ts b/apps/web/src/app/billing/services/trial-flow.service.ts
index 558851ad64c..aa6804926cd 100644
--- a/apps/web/src/app/billing/services/trial-flow.service.ts
+++ b/apps/web/src/app/billing/services/trial-flow.service.ts
@@ -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 {
- 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 {
- if (!org?.isOwner) {
+ private async promptForPaymentNavigation(
+ org: Organization,
+ isCanceled: boolean,
+ isUnpaid: boolean,
+ ): Promise {
+ 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;
+ }
+ }
}
diff --git a/apps/web/src/app/vault/individual-vault/vault-filter/components/vault-filter.component.ts b/apps/web/src/app/vault/individual-vault/vault-filter/components/vault-filter.component.ts
index efb1754c811..827ab39384a 100644
--- a/apps/web/src/app/vault/individual-vault/vault-filter/components/vault-filter.component.ts
+++ b/apps/web/src/app/vault/individual-vault/vault-filter/components/vault-filter.component.ts
@@ -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 {
- 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): Promise => {
const filter = this.activeFilter;
filter.resetFilter();
diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json
index 5e86e13f6ce..49448b9378f 100644
--- a/apps/web/src/locales/en/messages.json
+++ b/apps/web/src/locales/en/messages.json
@@ -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."
},
diff --git a/libs/common/src/billing/abstractions/billing-api.service.abstraction.ts b/libs/common/src/billing/abstractions/billing-api.service.abstraction.ts
index 8b82795fb50..20042541ebe 100644
--- a/libs/common/src/billing/abstractions/billing-api.service.abstraction.ts
+++ b/libs/common/src/billing/abstractions/billing-api.service.abstraction.ts
@@ -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;
+ restartSubscription: (
+ organizationId: string,
+ request: OrganizationCreateRequest,
+ ) => Promise;
}
diff --git a/libs/common/src/billing/abstractions/organization-billing.service.ts b/libs/common/src/billing/abstractions/organization-billing.service.ts
index ddcd61573a6..7c4e0a39f8f 100644
--- a/libs/common/src/billing/abstractions/organization-billing.service.ts
+++ b/libs/common/src/billing/abstractions/organization-billing.service.ts
@@ -57,4 +57,9 @@ export abstract class OrganizationBillingServiceAbstraction {
) => Promise;
startFree: (subscription: SubscriptionInformation) => Promise;
+
+ restartSubscription: (
+ organizationId: string,
+ subscription: SubscriptionInformation,
+ ) => Promise;
}
diff --git a/libs/common/src/billing/models/response/organization-billing-metadata.response.ts b/libs/common/src/billing/models/response/organization-billing-metadata.response.ts
index d9733aa80f2..758a49b647b 100644
--- a/libs/common/src/billing/models/response/organization-billing-metadata.response.ts
+++ b/libs/common/src/billing/models/response/organization-billing-metadata.response.ts
@@ -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");
}
}
diff --git a/libs/common/src/billing/services/billing-api.service.ts b/libs/common/src/billing/services/billing-api.service.ts
index cb69f294409..7ce5602f3cc 100644
--- a/libs/common/src/billing/services/billing-api.service.ts
+++ b/libs/common/src/billing/services/billing-api.service.ts
@@ -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 {
+ return await this.apiService.send(
+ "POST",
+ "/organizations/" + organizationId + "/billing/restart-subscription",
+ request,
+ true,
+ false,
+ );
+ }
+
private async execute(request: () => Promise): Promise {
try {
return await request();
diff --git a/libs/common/src/billing/services/organization-billing.service.ts b/libs/common/src/billing/services/organization-billing.service.ts
index a0b3782f1ad..80fabe92eb3 100644
--- a/libs/common/src/billing/services/organization-billing.service.ts
+++ b/libs/common/src/billing/services/organization-billing.service.ts
@@ -127,6 +127,25 @@ export class OrganizationBillingService implements OrganizationBillingServiceAbs
return response;
}
+ async restartSubscription(
+ organizationId: string,
+ subscription: SubscriptionInformation,
+ ): Promise {
+ 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 {
const [encryptedKey, key] = await this.keyService.makeOrgKey();
const [publicKey, encryptedPrivateKey] = await this.keyService.makeKeyPair(key);