mirror of
https://github.com/bitwarden/browser.git
synced 2024-12-22 16:29:09 +01:00
[AC-1607] Add offboarding survey to subscription pages (#7809)
* Add offboarding survey to subscription pages * Cleaning up unused code * Removing unused eslint suppression * Product updates * Jared's feedback
This commit is contained in:
parent
64381cbae0
commit
b239e3736f
@ -1,8 +1,11 @@
|
||||
import { Component, OnInit } from "@angular/core";
|
||||
import { Router } from "@angular/router";
|
||||
import { lastValueFrom, Observable } from "rxjs";
|
||||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { SubscriptionResponse } from "@bitwarden/common/billing/models/response/subscription.response";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { ConfigServiceAbstraction as ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction";
|
||||
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
|
||||
import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
@ -11,6 +14,11 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl
|
||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
||||
import { DialogService } from "@bitwarden/components";
|
||||
|
||||
import {
|
||||
OffboardingSurveyDialogResultType,
|
||||
openOffboardingSurvey,
|
||||
} from "../shared/offboarding-survey.component";
|
||||
|
||||
@Component({
|
||||
templateUrl: "user-subscription.component.html",
|
||||
})
|
||||
@ -26,6 +34,7 @@ export class UserSubscriptionComponent implements OnInit {
|
||||
|
||||
cancelPromise: Promise<any>;
|
||||
reinstatePromise: Promise<any>;
|
||||
presentUserWithOffboardingSurvey$: Observable<boolean>;
|
||||
|
||||
constructor(
|
||||
private stateService: StateService,
|
||||
@ -37,12 +46,16 @@ export class UserSubscriptionComponent implements OnInit {
|
||||
private fileDownloadService: FileDownloadService,
|
||||
private dialogService: DialogService,
|
||||
private environmentService: EnvironmentService,
|
||||
private configService: ConfigService,
|
||||
) {
|
||||
this.selfHosted = platformUtilsService.isSelfHost();
|
||||
this.cloudWebVaultUrl = this.environmentService.getCloudWebVaultUrl();
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
this.presentUserWithOffboardingSurvey$ = this.configService.getFeatureFlag$<boolean>(
|
||||
FeatureFlag.AC1607_PresentUserOffboardingSurvey,
|
||||
);
|
||||
await this.load();
|
||||
this.firstLoaded = true;
|
||||
}
|
||||
@ -93,36 +106,17 @@ export class UserSubscriptionComponent implements OnInit {
|
||||
}
|
||||
}
|
||||
|
||||
async cancel() {
|
||||
if (this.loading) {
|
||||
return;
|
||||
}
|
||||
cancel = async () => {
|
||||
const presentUserWithOffboardingSurvey = await this.configService.getFeatureFlag<boolean>(
|
||||
FeatureFlag.AC1607_PresentUserOffboardingSurvey,
|
||||
);
|
||||
|
||||
const confirmed = await this.dialogService.openSimpleDialog({
|
||||
title: { key: "cancelSubscription" },
|
||||
content: { key: "cancelConfirmation" },
|
||||
type: "warning",
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
return;
|
||||
if (presentUserWithOffboardingSurvey) {
|
||||
await this.cancelWithOffboardingSurvey();
|
||||
} else {
|
||||
await this.cancelWithWarning();
|
||||
}
|
||||
|
||||
try {
|
||||
this.cancelPromise = this.apiService.postCancelPremium();
|
||||
await this.cancelPromise;
|
||||
this.platformUtilsService.showToast(
|
||||
"success",
|
||||
null,
|
||||
this.i18nService.t("canceledSubscription"),
|
||||
);
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.load();
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
downloadLicense() {
|
||||
if (this.loading) {
|
||||
@ -166,6 +160,55 @@ export class UserSubscriptionComponent implements OnInit {
|
||||
}
|
||||
}
|
||||
|
||||
private cancelWithOffboardingSurvey = async () => {
|
||||
const reference = openOffboardingSurvey(this.dialogService, {
|
||||
data: {
|
||||
type: "User",
|
||||
},
|
||||
});
|
||||
|
||||
this.cancelPromise = lastValueFrom(reference.closed);
|
||||
|
||||
const result = await this.cancelPromise;
|
||||
|
||||
if (result === OffboardingSurveyDialogResultType.Closed) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.load();
|
||||
};
|
||||
|
||||
private async cancelWithWarning() {
|
||||
if (this.loading) {
|
||||
return;
|
||||
}
|
||||
|
||||
const confirmed = await this.dialogService.openSimpleDialog({
|
||||
title: { key: "cancelSubscription" },
|
||||
content: { key: "cancelConfirmation" },
|
||||
type: "warning",
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
this.cancelPromise = this.apiService.postCancelPremium();
|
||||
await this.cancelPromise;
|
||||
this.platformUtilsService.showToast(
|
||||
"success",
|
||||
null,
|
||||
this.i18nService.t("canceledSubscription"),
|
||||
);
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.load();
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
get subscriptionMarkedForCancel() {
|
||||
return (
|
||||
this.subscription != null && !this.subscription.cancelled && this.subscription.cancelAtEndDate
|
||||
|
@ -232,9 +232,28 @@
|
||||
<button
|
||||
bitButton
|
||||
buttonType="danger"
|
||||
[bitAction]="cancel"
|
||||
[bitAction]="cancelWithWarning"
|
||||
type="button"
|
||||
*ngIf="subscription && !subscription.cancelled && !subscriptionMarkedForCancel"
|
||||
*ngIf="
|
||||
subscription &&
|
||||
!subscription.cancelled &&
|
||||
!subscriptionMarkedForCancel &&
|
||||
!(presentUserWithOffboardingSurvey$ | async)
|
||||
"
|
||||
>
|
||||
{{ "cancelSubscription" | i18n }}
|
||||
</button>
|
||||
<button
|
||||
bitButton
|
||||
buttonType="danger"
|
||||
(click)="cancelWithOffboardingSurvey()"
|
||||
type="button"
|
||||
*ngIf="
|
||||
subscription &&
|
||||
!subscription.cancelled &&
|
||||
!subscriptionMarkedForCancel &&
|
||||
(presentUserWithOffboardingSurvey$ | async)
|
||||
"
|
||||
>
|
||||
{{ "cancelSubscription" | i18n }}
|
||||
</button>
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Component, OnDestroy, OnInit } from "@angular/core";
|
||||
import { ActivatedRoute } from "@angular/router";
|
||||
import { concatMap, firstValueFrom, Subject, takeUntil } from "rxjs";
|
||||
import { concatMap, firstValueFrom, lastValueFrom, Observable, Subject, takeUntil } from "rxjs";
|
||||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
|
||||
@ -11,11 +11,18 @@ import { PlanType } from "@bitwarden/common/billing/enums";
|
||||
import { OrganizationSubscriptionResponse } from "@bitwarden/common/billing/models/response/organization-subscription.response";
|
||||
import { BillingSubscriptionItemResponse } from "@bitwarden/common/billing/models/response/subscription.response";
|
||||
import { ProductType } from "@bitwarden/common/enums";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { ConfigServiceAbstraction as ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { DialogService } from "@bitwarden/components";
|
||||
|
||||
import {
|
||||
OffboardingSurveyDialogResultType,
|
||||
openOffboardingSurvey,
|
||||
} from "../shared/offboarding-survey.component";
|
||||
|
||||
import { BillingSyncApiKeyComponent } from "./billing-sync-api-key.component";
|
||||
import { SecretsManagerSubscriptionOptions } from "./sm-adjust-subscription.component";
|
||||
|
||||
@ -33,11 +40,10 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy
|
||||
showAdjustStorage = false;
|
||||
hasBillingSyncToken: boolean;
|
||||
showAdjustSecretsManager = false;
|
||||
|
||||
showSecretsManagerSubscribe = false;
|
||||
|
||||
firstLoaded = false;
|
||||
loading: boolean;
|
||||
presentUserWithOffboardingSurvey$: Observable<boolean>;
|
||||
|
||||
protected readonly teamsStarter = ProductType.TeamsStarter;
|
||||
|
||||
@ -52,6 +58,7 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy
|
||||
private organizationApiService: OrganizationApiServiceAbstraction,
|
||||
private route: ActivatedRoute,
|
||||
private dialogService: DialogService,
|
||||
private configService: ConfigService,
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
@ -71,6 +78,10 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy
|
||||
takeUntil(this.destroy$),
|
||||
)
|
||||
.subscribe();
|
||||
|
||||
this.presentUserWithOffboardingSurvey$ = this.configService.getFeatureFlag$<boolean>(
|
||||
FeatureFlag.AC1607_PresentUserOffboardingSurvey,
|
||||
);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
@ -168,10 +179,6 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy
|
||||
: 0;
|
||||
}
|
||||
|
||||
get storageProgressWidth() {
|
||||
return this.storagePercentage < 5 ? 5 : 0;
|
||||
}
|
||||
|
||||
get billingInterval() {
|
||||
const monthly = !this.sub.plan.isAnnual;
|
||||
return monthly ? "month" : "year";
|
||||
@ -211,10 +218,6 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy
|
||||
return this.sub.plan.PasswordManager.hasAdditionalSeatsOption;
|
||||
}
|
||||
|
||||
get isAdmin() {
|
||||
return this.userOrg.isAdmin;
|
||||
}
|
||||
|
||||
get isSponsoredSubscription(): boolean {
|
||||
return this.sub.subscription?.items.some((i) => i.sponsoredSubscriptionItem);
|
||||
}
|
||||
@ -270,7 +273,24 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy
|
||||
);
|
||||
}
|
||||
|
||||
cancel = async () => {
|
||||
cancelWithOffboardingSurvey = async () => {
|
||||
const reference = openOffboardingSurvey(this.dialogService, {
|
||||
data: {
|
||||
type: "Organization",
|
||||
id: this.organizationId,
|
||||
},
|
||||
});
|
||||
|
||||
const result = await lastValueFrom(reference.closed);
|
||||
|
||||
if (result === OffboardingSurveyDialogResultType.Closed) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.load();
|
||||
};
|
||||
|
||||
cancelWithWarning = async () => {
|
||||
if (this.loading) {
|
||||
return;
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import { AddCreditComponent } from "./add-credit.component";
|
||||
import { AdjustPaymentComponent } from "./adjust-payment.component";
|
||||
import { AdjustStorageComponent } from "./adjust-storage.component";
|
||||
import { BillingHistoryComponent } from "./billing-history.component";
|
||||
import { OffboardingSurveyComponent } from "./offboarding-survey.component";
|
||||
import { PaymentMethodComponent } from "./payment-method.component";
|
||||
import { PaymentComponent } from "./payment.component";
|
||||
import { SecretsManagerSubscribeComponent } from "./sm-subscribe.component";
|
||||
@ -22,16 +23,17 @@ import { UpdateLicenseComponent } from "./update-license.component";
|
||||
PaymentMethodComponent,
|
||||
SecretsManagerSubscribeComponent,
|
||||
UpdateLicenseComponent,
|
||||
OffboardingSurveyComponent,
|
||||
],
|
||||
exports: [
|
||||
SharedModule,
|
||||
PaymentComponent,
|
||||
TaxInfoComponent,
|
||||
|
||||
AdjustStorageComponent,
|
||||
BillingHistoryComponent,
|
||||
SecretsManagerSubscribeComponent,
|
||||
UpdateLicenseComponent,
|
||||
OffboardingSurveyComponent,
|
||||
],
|
||||
})
|
||||
export class BillingSharedModule {}
|
||||
|
@ -0,0 +1,37 @@
|
||||
<form [formGroup]="formGroup" [bitSubmit]="submit">
|
||||
<bit-dialog>
|
||||
<span bitDialogTitle>
|
||||
{{ "cancelSubscription" | i18n }}
|
||||
</span>
|
||||
<div bitDialogContent>
|
||||
<p>{{ "sorryToSeeYouGo" | i18n }}</p>
|
||||
<bit-form-field>
|
||||
<bit-label>
|
||||
{{ "selectCancellationReason" | i18n }}
|
||||
</bit-label>
|
||||
<select bitInput formControlName="reason">
|
||||
<option *ngFor="let reason of reasons" [ngValue]="reason.value">
|
||||
{{ reason.text }}
|
||||
</option>
|
||||
</select>
|
||||
</bit-form-field>
|
||||
<bit-form-field>
|
||||
<bit-label>
|
||||
{{ "anyOtherFeedback" | i18n }}
|
||||
</bit-label>
|
||||
<textarea rows="4" bitInput formControlName="feedback"></textarea>
|
||||
<bit-hint>{{
|
||||
"charactersCurrentAndMaximum" | i18n: formGroup.value.feedback.length : MaxFeedbackLength
|
||||
}}</bit-hint>
|
||||
</bit-form-field>
|
||||
</div>
|
||||
<ng-container bitDialogFooter>
|
||||
<button bitButton bitFormButton buttonType="primary" type="submit">
|
||||
{{ "cancelSubscription" | i18n }}
|
||||
</button>
|
||||
<button bitButton buttonType="secondary" type="button" [bitDialogClose]="ResultType.Closed">
|
||||
{{ "close" | i18n }}
|
||||
</button>
|
||||
</ng-container>
|
||||
</bit-dialog>
|
||||
</form>
|
117
apps/web/src/app/billing/shared/offboarding-survey.component.ts
Normal file
117
apps/web/src/app/billing/shared/offboarding-survey.component.ts
Normal file
@ -0,0 +1,117 @@
|
||||
import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog";
|
||||
import { Component, Inject } from "@angular/core";
|
||||
import { FormBuilder, Validators } from "@angular/forms";
|
||||
|
||||
import { BillingApiServiceAbstraction as BillingApiService } from "@bitwarden/common/billing/abstractions/billilng-api.service.abstraction";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { DialogService } from "@bitwarden/components";
|
||||
|
||||
type UserOffboardingParams = {
|
||||
type: "User";
|
||||
};
|
||||
|
||||
type OrganizationOffboardingParams = {
|
||||
type: "Organization";
|
||||
id: string;
|
||||
};
|
||||
|
||||
export type OffboardingSurveyDialogParams = UserOffboardingParams | OrganizationOffboardingParams;
|
||||
|
||||
export enum OffboardingSurveyDialogResultType {
|
||||
Closed = "closed",
|
||||
Submitted = "submitted",
|
||||
}
|
||||
|
||||
type Reason = {
|
||||
value: string;
|
||||
text: string;
|
||||
};
|
||||
|
||||
export const openOffboardingSurvey = (
|
||||
dialogService: DialogService,
|
||||
dialogConfig: DialogConfig<OffboardingSurveyDialogParams>,
|
||||
) =>
|
||||
dialogService.open<OffboardingSurveyDialogResultType, OffboardingSurveyDialogParams>(
|
||||
OffboardingSurveyComponent,
|
||||
dialogConfig,
|
||||
);
|
||||
|
||||
@Component({
|
||||
selector: "app-cancel-subscription-form",
|
||||
templateUrl: "offboarding-survey.component.html",
|
||||
})
|
||||
export class OffboardingSurveyComponent {
|
||||
protected ResultType = OffboardingSurveyDialogResultType;
|
||||
protected readonly MaxFeedbackLength = 400;
|
||||
|
||||
protected readonly reasons: Reason[] = [
|
||||
{
|
||||
value: null,
|
||||
text: this.i18nService.t("selectPlaceholder"),
|
||||
},
|
||||
{
|
||||
value: "missing_features",
|
||||
text: this.i18nService.t("missingFeatures"),
|
||||
},
|
||||
{
|
||||
value: "switched_service",
|
||||
text: this.i18nService.t("movingToAnotherTool"),
|
||||
},
|
||||
{
|
||||
value: "too_complex",
|
||||
text: this.i18nService.t("tooDifficultToUse"),
|
||||
},
|
||||
{
|
||||
value: "unused",
|
||||
text: this.i18nService.t("notUsingEnough"),
|
||||
},
|
||||
{
|
||||
value: "too_expensive",
|
||||
text: this.i18nService.t("tooExpensive"),
|
||||
},
|
||||
{
|
||||
value: "other",
|
||||
text: this.i18nService.t("other"),
|
||||
},
|
||||
];
|
||||
|
||||
protected formGroup = this.formBuilder.group({
|
||||
reason: [this.reasons[0].value, [Validators.required]],
|
||||
feedback: ["", [Validators.maxLength(this.MaxFeedbackLength)]],
|
||||
});
|
||||
|
||||
constructor(
|
||||
@Inject(DIALOG_DATA) private dialogParams: OffboardingSurveyDialogParams,
|
||||
private dialogRef: DialogRef<OffboardingSurveyDialogResultType>,
|
||||
private formBuilder: FormBuilder,
|
||||
private billingApiService: BillingApiService,
|
||||
private i18nService: I18nService,
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
) {}
|
||||
|
||||
submit = async () => {
|
||||
this.formGroup.markAllAsTouched();
|
||||
|
||||
if (this.formGroup.invalid) {
|
||||
return;
|
||||
}
|
||||
|
||||
const request = {
|
||||
reason: this.formGroup.value.reason,
|
||||
feedback: this.formGroup.value.feedback,
|
||||
};
|
||||
|
||||
this.dialogParams.type === "Organization"
|
||||
? await this.billingApiService.cancelOrganizationSubscription(this.dialogParams.id, request)
|
||||
: await this.billingApiService.cancelPremiumUserSubscription(request);
|
||||
|
||||
this.platformUtilsService.showToast(
|
||||
"success",
|
||||
null,
|
||||
this.i18nService.t("canceledSubscription"),
|
||||
);
|
||||
|
||||
this.dialogRef.close(this.ResultType.Submitted);
|
||||
};
|
||||
}
|
@ -7525,5 +7525,37 @@
|
||||
},
|
||||
"confirmCollectionEnhancementsDialogContent": {
|
||||
"message": "Turning on this feature will deprecate the manager role and replace it with a Can manage permission. This will take a few moments. Do not make any organization changes until it is complete. Are you sure you want to proceed?"
|
||||
},
|
||||
"sorryToSeeYouGo": {
|
||||
"message": "Sorry to see you go! Help improve Bitwarden by sharing why you're canceling.",
|
||||
"description": "A message shown to users as part of an offboarding survey asking them to provide more information on their subscription cancelation."
|
||||
},
|
||||
"selectCancellationReason": {
|
||||
"message": "Select a reason for canceling",
|
||||
"description": "Used as a form field label for a select input on the offboarding survey."
|
||||
},
|
||||
"anyOtherFeedback": {
|
||||
"message": "Is there any other feedback you'd like to share?",
|
||||
"description": "Used as a form field label for a textarea input on the offboarding survey."
|
||||
},
|
||||
"missingFeatures": {
|
||||
"message": "Missing features",
|
||||
"description": "An option for the offboarding survey shown when a user cancels their subscription."
|
||||
},
|
||||
"movingToAnotherTool": {
|
||||
"message": "Moving to another tool",
|
||||
"description": "An option for the offboarding survey shown when a user cancels their subscription."
|
||||
},
|
||||
"tooDifficultToUse": {
|
||||
"message": "Too difficult to use",
|
||||
"description": "An option for the offboarding survey shown when a user cancels their subscription."
|
||||
},
|
||||
"notUsingEnough": {
|
||||
"message": "Not using enough",
|
||||
"description": "An option for the offboarding survey shown when a user cancels their subscription."
|
||||
},
|
||||
"tooExpensive": {
|
||||
"message": "Too expensive",
|
||||
"description": "An option for the offboarding survey shown when a user cancels their subscription."
|
||||
}
|
||||
}
|
||||
|
@ -82,8 +82,10 @@ import { UserVerificationService } from "@bitwarden/common/auth/services/user-ve
|
||||
import { WebAuthnLoginApiService } from "@bitwarden/common/auth/services/webauthn-login/webauthn-login-api.service";
|
||||
import { WebAuthnLoginPrfCryptoService } from "@bitwarden/common/auth/services/webauthn-login/webauthn-login-prf-crypto.service";
|
||||
import { WebAuthnLoginService } from "@bitwarden/common/auth/services/webauthn-login/webauthn-login.service";
|
||||
import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/billilng-api.service.abstraction";
|
||||
import { BillingBannerServiceAbstraction } from "@bitwarden/common/billing/abstractions/billing-banner.service.abstraction";
|
||||
import { OrganizationBillingServiceAbstraction } from "@bitwarden/common/billing/abstractions/organization-billing.service";
|
||||
import { BillingApiService } from "@bitwarden/common/billing/services/billing-api.service";
|
||||
import { BillingBannerService } from "@bitwarden/common/billing/services/billing-banner.service";
|
||||
import { OrganizationBillingService } from "@bitwarden/common/billing/services/organization-billing.service";
|
||||
import { AppIdService as AppIdServiceAbstraction } from "@bitwarden/common/platform/abstractions/app-id.service";
|
||||
@ -916,6 +918,11 @@ import { ModalService } from "./modal.service";
|
||||
useClass: VaultSettingsService,
|
||||
deps: [StateProvider],
|
||||
},
|
||||
{
|
||||
provide: BillingApiServiceAbstraction,
|
||||
useClass: BillingApiService,
|
||||
deps: [ApiServiceAbstraction],
|
||||
},
|
||||
],
|
||||
})
|
||||
export class JslibServicesModule {}
|
||||
|
@ -0,0 +1,9 @@
|
||||
import { SubscriptionCancellationRequest } from "../../billing/models/request/subscription-cancellation.request";
|
||||
|
||||
export abstract class BillingApiServiceAbstraction {
|
||||
cancelOrganizationSubscription: (
|
||||
organizationId: string,
|
||||
request: SubscriptionCancellationRequest,
|
||||
) => Promise<void>;
|
||||
cancelPremiumUserSubscription: (request: SubscriptionCancellationRequest) => Promise<void>;
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
export type SubscriptionCancellationRequest = {
|
||||
reason: string;
|
||||
feedback?: string;
|
||||
};
|
24
libs/common/src/billing/services/billing-api.service.ts
Normal file
24
libs/common/src/billing/services/billing-api.service.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import { ApiService } from "../../abstractions/api.service";
|
||||
import { BillingApiServiceAbstraction } from "../../billing/abstractions/billilng-api.service.abstraction";
|
||||
import { SubscriptionCancellationRequest } from "../../billing/models/request/subscription-cancellation.request";
|
||||
|
||||
export class BillingApiService implements BillingApiServiceAbstraction {
|
||||
constructor(private apiService: ApiService) {}
|
||||
|
||||
cancelOrganizationSubscription(
|
||||
organizationId: string,
|
||||
request: SubscriptionCancellationRequest,
|
||||
): Promise<void> {
|
||||
return this.apiService.send(
|
||||
"POST",
|
||||
"/organizations/" + organizationId + "/cancel",
|
||||
request,
|
||||
true,
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
cancelPremiumUserSubscription(request: SubscriptionCancellationRequest): Promise<void> {
|
||||
return this.apiService.send("POST", "/accounts/cancel-premium", request, true, false);
|
||||
}
|
||||
}
|
@ -7,6 +7,7 @@ export enum FeatureFlag {
|
||||
GeneratorToolsModernization = "generator-tools-modernization",
|
||||
KeyRotationImprovements = "key-rotation-improvements",
|
||||
FlexibleCollectionsMigration = "flexible-collections-migration",
|
||||
AC1607_PresentUserOffboardingSurvey = "AC-1607_present-user-offboarding-survey",
|
||||
}
|
||||
|
||||
// Replace this with a type safe lookup of the feature flag values in PM-2282
|
||||
|
Loading…
Reference in New Issue
Block a user