Skip to content

Commit

Permalink
Changes to restart cancelled org (#12730)
Browse files Browse the repository at this point in the history
  • Loading branch information
cyprain-okeke authored Jan 7, 2025
1 parent 966e8d3 commit 02556c1
Show file tree
Hide file tree
Showing 11 changed files with 261 additions and 57 deletions.
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
<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>
<div class="tw-mb-3 tw-flex tw-justify-between">
<span class="tw-text-lg tw-pr-1 tw-font-bold">{{ "selectAPlan" | i18n }}</span>
<span [hidden]="isSubscriptionCanceled" class="tw-text-lg tw-pr-1 tw-font-bold">{{
"selectAPlan" | i18n
}}</span>
<!-- Discount Badge -->
<div class="tw-flex tw-items-center tw-gap-2">
<span
class="tw-mr-1"
[hidden]="isSubscriptionCanceled"
*ngIf="
this.discountPercentageFromSub > 0
? discountPercentageFromSub
Expand Down Expand Up @@ -69,7 +72,10 @@
>
<div class="tw-relative">
<div
*ngIf="selectableProduct.productTier === productTypes.Enterprise"
*ngIf="
selectableProduct.productTier === productTypes.Enterprise &&
!isSubscriptionCanceled
"
class="tw-bg-secondary-100 tw-text-center !tw-border-0 tw-text-sm tw-font-bold tw-py-1"
[ngClass]="{
'tw-bg-primary-700 !tw-text-contrast': selectableProduct === selectedPlan,
Expand Down Expand Up @@ -330,9 +336,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
Expand Down
118 changes: 113 additions & 5 deletions apps/web/src/app/billing/organizations/change-plan-dialog.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,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 { TaxServiceAbstraction } from "@bitwarden/common/billing/abstractions/tax.service.abstraction";
import {
PaymentMethodType,
Expand All @@ -49,6 +56,7 @@ 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";

Expand Down Expand Up @@ -89,6 +97,8 @@ interface OnSuccessArgs {

@Component({
templateUrl: "./change-plan-dialog.component.html",
standalone: true,
imports: [BillingSharedModule],
})
export class ChangePlanDialogComponent implements OnInit, OnDestroy {
@ViewChild(PaymentComponent) paymentComponent: PaymentComponent;
Expand Down Expand Up @@ -163,6 +173,7 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
organization: Organization;
sub: OrganizationSubscriptionResponse;
billing: BillingResponse;
dialogHeaderName: string;
currentPlanName: string;
showPayment: boolean = false;
totalOpened: boolean = false;
Expand All @@ -174,6 +185,7 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
paymentSource?: PaymentSourceResponse;

deprecateStripeSourcesAPI: boolean;
isSubscriptionCanceled: boolean = false;

private destroy$ = new Subject<void>();

Expand All @@ -196,6 +208,7 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
private configService: ConfigService,
private billingApiService: BillingApiServiceAbstraction,
private taxService: TaxServiceAbstraction,
private organizationBillingService: OrganizationBillingService,
) {}

async ngOnInit(): Promise<void> {
Expand All @@ -208,6 +221,7 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
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;
Expand Down Expand Up @@ -281,6 +295,20 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
this.refreshSalesTax();
}

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),
);
}

setInitialPlanSelection() {
this.focusedIndex = this.selectableProducts.length - 1;
this.selectPlan(this.getPlanByType(ProductTierType.Enterprise));
Expand Down Expand Up @@ -388,6 +416,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",
Expand All @@ -409,7 +450,7 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
return;
}

if (plan === this.currentPlan) {
if (plan === this.currentPlan && !this.isSubscriptionCanceled) {
return;
}
this.selectedPlan = plan;
Expand Down Expand Up @@ -446,6 +487,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,
Expand Down Expand Up @@ -692,11 +738,18 @@ 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();
Expand Down Expand Up @@ -726,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) {
Expand Down Expand Up @@ -802,6 +893,18 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
return text;
}

private getBillingInformationFromTaxInfoComponent(): BillingInformation {
return {
country: this.taxInformation.country,
postalCode: this.taxInformation.postalCode,
taxId: this.taxInformation.taxId,
addressLine1: this.taxInformation.line1,
addressLine2: this.taxInformation.line2,
city: this.taxInformation.city,
state: this.taxInformation.state,
};
}

private buildSecretsManagerRequest(request: OrganizationUpgradeRequest): void {
request.useSecretsManager = this.organization.useSecretsManager;
if (!this.organization.useSecretsManager) {
Expand Down Expand Up @@ -997,6 +1100,11 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
}

protected canUpdatePaymentInformation(): boolean {
return this.upgradeRequiresPaymentMethod || this.showPayment || this.isPaymentSourceEmpty();
return (
this.upgradeRequiresPaymentMethod ||
this.showPayment ||
this.isPaymentSourceEmpty() ||
this.isSubscriptionCanceled
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -44,7 +43,6 @@ import { SubscriptionStatusComponent } from "./subscription-status.component";
SecretsManagerSubscribeStandaloneComponent,
SubscriptionHiddenComponent,
SubscriptionStatusComponent,
ChangePlanDialogComponent,
OrganizationPaymentMethodComponent,
],
})
Expand Down
Loading

0 comments on commit 02556c1

Please sign in to comment.