diff --git a/apps/web/src/app/billing/organizations/payment-method/organization-payment-method.component.ts b/apps/web/src/app/billing/organizations/payment-method/organization-payment-method.component.ts index bcc497113eb..adbda79caf2 100644 --- a/apps/web/src/app/billing/organizations/payment-method/organization-payment-method.component.ts +++ b/apps/web/src/app/billing/organizations/payment-method/organization-payment-method.component.ts @@ -33,6 +33,10 @@ import { AdjustPaymentDialogComponent, AdjustPaymentDialogResultType, } from "../../shared/adjust-payment-dialog/adjust-payment-dialog.component"; +import { + TrialPaymentMethodDialogComponent, + TrialPaymentMethodDialogResultType, +} from "../../shared/trial-subscription-dialog/trial-payment-method-dialog.component"; import { FreeTrial } from "../../types/free-trial"; @Component({ @@ -190,15 +194,15 @@ export class OrganizationPaymentMethodComponent implements OnDestroy { }; changePayment = async () => { - const dialogRef = AdjustPaymentDialogComponent.open(this.dialogService, { + const dialogRef = TrialPaymentMethodDialogComponent.open(this.dialogService, { data: { - initialPaymentMethod: this.paymentSource?.type, organizationId: this.organizationId, - productTier: this.organization?.productTierType, + subscription: this.organizationSubscriptionResponse, + productTierType: this.organization.productTierType, }, }); const result = await lastValueFrom(dialogRef.closed); - if (result === AdjustPaymentDialogResultType.Submitted) { + if (result === TrialPaymentMethodDialogResultType.Submitted) { this.location.replaceState(this.location.path(), "", {}); if (this.launchPaymentModalAutomatically && !this.organization.enabled) { await this.syncService.fullSync(true); diff --git a/apps/web/src/app/billing/shared/trial-subscription-dialog/trial-payment-method-dialog.component.html b/apps/web/src/app/billing/shared/trial-subscription-dialog/trial-payment-method-dialog.component.html new file mode 100644 index 00000000000..cd27a443570 --- /dev/null +++ b/apps/web/src/app/billing/shared/trial-subscription-dialog/trial-payment-method-dialog.component.html @@ -0,0 +1,836 @@ +
diff --git a/apps/web/src/app/billing/shared/trial-subscription-dialog/trial-payment-method-dialog.component.ts b/apps/web/src/app/billing/shared/trial-subscription-dialog/trial-payment-method-dialog.component.ts new file mode 100644 index 00000000000..c6800b62e0b --- /dev/null +++ b/apps/web/src/app/billing/shared/trial-subscription-dialog/trial-payment-method-dialog.component.ts @@ -0,0 +1,767 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore +import { + Component, + EventEmitter, + Inject, + Input, + OnDestroy, + OnInit, + Output, + ViewChild, +} from "@angular/core"; +import { FormBuilder, Validators } from "@angular/forms"; +import { Router } from "@angular/router"; +import { firstValueFrom, map, Subject, switchMap, takeUntil } from "rxjs"; + +import { ManageTaxInformationComponent } from "@bitwarden/angular/billing/components"; +import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; +import { + getOrganizationById, + OrganizationService, +} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +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 { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; +import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions"; +import { TaxServiceAbstraction } from "@bitwarden/common/billing/abstractions/tax.service.abstraction"; +import { + PaymentMethodType, + PlanInterval, + PlanType, + ProductTierType, +} from "@bitwarden/common/billing/enums"; +import { TaxInformation } from "@bitwarden/common/billing/models/domain"; +import { ExpandedTaxInfoUpdateRequest } from "@bitwarden/common/billing/models/request/expanded-tax-info-update.request"; +import { PaymentRequest } from "@bitwarden/common/billing/models/request/payment.request"; +import { PreviewOrganizationInvoiceRequest } from "@bitwarden/common/billing/models/request/preview-organization-invoice.request"; +import { UpdatePaymentMethodRequest } from "@bitwarden/common/billing/models/request/update-payment-method.request"; +import { BillingResponse } from "@bitwarden/common/billing/models/response/billing.response"; +import { OrganizationSubscriptionResponse } from "@bitwarden/common/billing/models/response/organization-subscription.response"; +import { PaymentSourceResponse } from "@bitwarden/common/billing/models/response/payment-source.response"; +import { PlanResponse } from "@bitwarden/common/billing/models/response/plan.response"; +import { ListResponse } from "@bitwarden/common/models/response/list.response"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { + DIALOG_DATA, + DialogConfig, + DialogRef, + DialogService, + ToastService, +} from "@bitwarden/components"; + +import { BillingSharedModule } from "../billing-shared.module"; +import { PaymentComponent } from "../payment/payment.component"; + +type TrialPaymentMethodParams = { + organizationId: string; + subscription: OrganizationSubscriptionResponse; + productTierType: ProductTierType; + initialPaymentMethod?: PaymentMethodType; +}; + +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums +export enum TrialPaymentMethodDialogResultType { + Closed = "closed", + Submitted = "submitted", +} + +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums +export enum PlanCardState { + Selected = "selected", + NotSelected = "not_selected", + Disabled = "disabled", +} + +type PlanCard = { + name: string; + selected: boolean; +}; + +interface OnSuccessArgs { + organizationId: string; +} + +@Component({ + templateUrl: "./trial-payment-method-dialog.component.html", + imports: [BillingSharedModule], +}) +export class TrialPaymentMethodDialogComponent implements OnInit, OnDestroy { + @ViewChild(PaymentComponent) paymentComponent: PaymentComponent; + @ViewChild(ManageTaxInformationComponent) taxComponent: ManageTaxInformationComponent; + + @Input() acceptingSponsorship = false; + @Input() organizationId: string; + @Input() showFree = false; + @Input() showCancel = false; + + @Input() + get productTier(): ProductTierType { + return this._productTier; + } + + set productTier(product: ProductTierType) { + this._productTier = product; + this.formGroup?.controls?.productTier?.setValue(product); + } + + protected estimatedTax: number = 0; + private _productTier = ProductTierType.Free; + + @Input() + get plan(): PlanType { + return this._plan; + } + + set plan(plan: PlanType) { + this._plan = plan; + this.formGroup?.controls?.plan?.setValue(plan); + } + + private _plan = PlanType.Free; + @Input() providerId?: string; + @Output() onSuccess = new EventEmitter