mirror of
https://github.com/bitwarden/browser
synced 2025-12-18 17:23:37 +00:00
[PM-25463] Work towards complete usage of Payments domain (#16532)
* Use payment domain * Fixing lint and test issue * Fix organization plans tax issue * PM-26297: Use existing billing address for tax calculation if it exists * PM-26344: Check existing payment method on submit
This commit is contained in:
@@ -39,12 +39,7 @@
|
||||
*ngIf="canAccessBilling$ | async"
|
||||
>
|
||||
<bit-nav-item [text]="'subscription' | i18n" route="billing/subscription"></bit-nav-item>
|
||||
@if (managePaymentDetailsOutsideCheckout$ | async) {
|
||||
<bit-nav-item
|
||||
[text]="'paymentDetails' | i18n"
|
||||
route="billing/payment-details"
|
||||
></bit-nav-item>
|
||||
}
|
||||
<bit-nav-item [text]="'paymentDetails' | i18n" route="billing/payment-details"></bit-nav-item>
|
||||
<bit-nav-item [text]="'billingHistory' | i18n" route="billing/history"></bit-nav-item>
|
||||
</bit-nav-group>
|
||||
<bit-nav-item
|
||||
|
||||
@@ -47,7 +47,6 @@ export class ProvidersLayoutComponent implements OnInit, OnDestroy {
|
||||
protected canAccessBilling$: Observable<boolean>;
|
||||
|
||||
protected clientsTranslationKey$: Observable<string>;
|
||||
protected managePaymentDetailsOutsideCheckout$: Observable<boolean>;
|
||||
protected providerPortalTakeover$: Observable<boolean>;
|
||||
|
||||
protected subscriber$: Observable<NonIndividualSubscriber>;
|
||||
@@ -100,10 +99,6 @@ export class ProvidersLayoutComponent implements OnInit, OnDestroy {
|
||||
),
|
||||
);
|
||||
|
||||
this.managePaymentDetailsOutsideCheckout$ = this.configService.getFeatureFlag$(
|
||||
FeatureFlag.PM21881_ManagePaymentDetailsOutsideCheckout,
|
||||
);
|
||||
|
||||
this.provider$
|
||||
.pipe(
|
||||
switchMap((provider) =>
|
||||
|
||||
@@ -7,14 +7,18 @@ import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import { CardComponent, ScrollLayoutDirective, SearchModule } from "@bitwarden/components";
|
||||
import { DangerZoneComponent } from "@bitwarden/web-vault/app/auth/settings/account/danger-zone.component";
|
||||
import { OrganizationPlansComponent } from "@bitwarden/web-vault/app/billing";
|
||||
import { PaymentComponent } from "@bitwarden/web-vault/app/billing/shared/payment/payment.component";
|
||||
import { VerifyBankAccountComponent } from "@bitwarden/web-vault/app/billing/shared/verify-bank-account/verify-bank-account.component";
|
||||
import {
|
||||
EnterBillingAddressComponent,
|
||||
EnterPaymentMethodComponent,
|
||||
} from "@bitwarden/web-vault/app/billing/payment/components";
|
||||
import { OssModule } from "@bitwarden/web-vault/app/oss.module";
|
||||
|
||||
import {
|
||||
CreateClientDialogComponent,
|
||||
InvoicesComponent,
|
||||
ManageClientNameDialogComponent,
|
||||
ManageClientSubscriptionDialogComponent,
|
||||
NoInvoicesComponent,
|
||||
ProviderBillingHistoryComponent,
|
||||
ProviderSubscriptionComponent,
|
||||
ProviderSubscriptionStatusComponent,
|
||||
@@ -52,11 +56,11 @@ import { VerifyRecoverDeleteProviderComponent } from "./verify-recover-delete-pr
|
||||
ProvidersLayoutComponent,
|
||||
DangerZoneComponent,
|
||||
ScrollingModule,
|
||||
VerifyBankAccountComponent,
|
||||
CardComponent,
|
||||
ScrollLayoutDirective,
|
||||
PaymentComponent,
|
||||
ProviderWarningsModule,
|
||||
EnterPaymentMethodComponent,
|
||||
EnterBillingAddressComponent,
|
||||
],
|
||||
declarations: [
|
||||
AcceptProviderComponent,
|
||||
@@ -72,8 +76,10 @@ import { VerifyRecoverDeleteProviderComponent } from "./verify-recover-delete-pr
|
||||
AddEditMemberDialogComponent,
|
||||
AddExistingOrganizationDialogComponent,
|
||||
CreateClientDialogComponent,
|
||||
InvoicesComponent,
|
||||
ManageClientNameDialogComponent,
|
||||
ManageClientSubscriptionDialogComponent,
|
||||
NoInvoicesComponent,
|
||||
ProviderBillingHistoryComponent,
|
||||
ProviderSubscriptionComponent,
|
||||
ProviderSubscriptionStatusComponent,
|
||||
|
||||
@@ -29,13 +29,11 @@
|
||||
</div>
|
||||
</div>
|
||||
<h2 class="tw-mt-5">{{ "paymentMethod" | i18n }}</h2>
|
||||
<app-payment
|
||||
[showAccountCredit]="false"
|
||||
[bankAccountWarningOverride]="
|
||||
'verifyProviderBankAccountWithStatementDescriptorWarning' | i18n
|
||||
"
|
||||
/>
|
||||
<app-manage-tax-information />
|
||||
<app-enter-payment-method [group]="formGroup.controls.paymentMethod"></app-enter-payment-method>
|
||||
<app-enter-billing-address
|
||||
[group]="formGroup.controls.billingAddress"
|
||||
[scenario]="{ type: 'checkout', supportsTaxId: true }"
|
||||
></app-enter-billing-address>
|
||||
<button class="tw-mt-8" bitButton bitFormButton buttonType="primary" type="submit">
|
||||
{{ "submit" | i18n }}
|
||||
</button>
|
||||
|
||||
@@ -1,25 +1,24 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { Component, OnDestroy, OnInit, ViewChild } from "@angular/core";
|
||||
import { FormBuilder, Validators } from "@angular/forms";
|
||||
import { ActivatedRoute, Router } from "@angular/router";
|
||||
import { firstValueFrom, Subject, switchMap } from "rxjs";
|
||||
import { first, takeUntil } from "rxjs/operators";
|
||||
|
||||
import { ManageTaxInformationComponent } from "@bitwarden/angular/billing/components";
|
||||
import { ProviderApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/provider/provider-api.service.abstraction";
|
||||
import { ProviderSetupRequest } from "@bitwarden/common/admin-console/models/request/provider/provider-setup.request";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||
import { PaymentMethodType } from "@bitwarden/common/billing/enums";
|
||||
import { ExpandedTaxInfoUpdateRequest } from "@bitwarden/common/billing/models/request/expanded-tax-info-update.request";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service";
|
||||
import { ProviderKey } from "@bitwarden/common/types/key";
|
||||
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
|
||||
import { ToastService } from "@bitwarden/components";
|
||||
import { KeyService } from "@bitwarden/key-management";
|
||||
import { PaymentComponent } from "@bitwarden/web-vault/app/billing/shared/payment/payment.component";
|
||||
import {
|
||||
EnterBillingAddressComponent,
|
||||
EnterPaymentMethodComponent,
|
||||
getBillingAddressFromForm,
|
||||
} from "@bitwarden/web-vault/app/billing/payment/components";
|
||||
|
||||
@Component({
|
||||
selector: "provider-setup",
|
||||
@@ -27,16 +26,17 @@ import { PaymentComponent } from "@bitwarden/web-vault/app/billing/shared/paymen
|
||||
standalone: false,
|
||||
})
|
||||
export class SetupComponent implements OnInit, OnDestroy {
|
||||
@ViewChild(PaymentComponent) paymentComponent: PaymentComponent;
|
||||
@ViewChild(ManageTaxInformationComponent) taxInformationComponent: ManageTaxInformationComponent;
|
||||
@ViewChild(EnterPaymentMethodComponent) enterPaymentMethodComponent!: EnterPaymentMethodComponent;
|
||||
|
||||
loading = true;
|
||||
providerId: string;
|
||||
token: string;
|
||||
providerId!: string;
|
||||
token!: string;
|
||||
|
||||
protected formGroup = this.formBuilder.group({
|
||||
name: ["", Validators.required],
|
||||
billingEmail: ["", [Validators.required, Validators.email]],
|
||||
paymentMethod: EnterPaymentMethodComponent.getFormGroup(),
|
||||
billingAddress: EnterBillingAddressComponent.getFormGroup(),
|
||||
});
|
||||
|
||||
private destroy$ = new Subject<void>();
|
||||
@@ -69,7 +69,7 @@ export class SetupComponent implements OnInit, OnDestroy {
|
||||
if (error) {
|
||||
this.toastService.showToast({
|
||||
variant: "error",
|
||||
title: null,
|
||||
title: "",
|
||||
message: this.i18nService.t("emergencyInviteAcceptFailed"),
|
||||
timeout: 10000,
|
||||
});
|
||||
@@ -95,6 +95,7 @@ export class SetupComponent implements OnInit, OnDestroy {
|
||||
replaceUrl: true,
|
||||
});
|
||||
}
|
||||
|
||||
this.loading = false;
|
||||
} catch (error) {
|
||||
this.validationService.showError(error);
|
||||
@@ -115,10 +116,7 @@ export class SetupComponent implements OnInit, OnDestroy {
|
||||
try {
|
||||
this.formGroup.markAllAsTouched();
|
||||
|
||||
const paymentValid = this.paymentComponent.validate();
|
||||
const taxInformationValid = this.taxInformationComponent.validate();
|
||||
|
||||
if (!paymentValid || !taxInformationValid || !this.formGroup.valid) {
|
||||
if (this.formGroup.invalid) {
|
||||
return;
|
||||
}
|
||||
const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$));
|
||||
@@ -126,29 +124,24 @@ export class SetupComponent implements OnInit, OnDestroy {
|
||||
const key = providerKey[0].encryptedString;
|
||||
|
||||
const request = new ProviderSetupRequest();
|
||||
request.name = this.formGroup.value.name;
|
||||
request.billingEmail = this.formGroup.value.billingEmail;
|
||||
request.name = this.formGroup.value.name!;
|
||||
request.billingEmail = this.formGroup.value.billingEmail!;
|
||||
request.token = this.token;
|
||||
request.key = key;
|
||||
request.key = key!;
|
||||
|
||||
request.taxInfo = new ExpandedTaxInfoUpdateRequest();
|
||||
const taxInformation = this.taxInformationComponent.getTaxInformation();
|
||||
const paymentMethod = await this.enterPaymentMethodComponent.tokenize();
|
||||
if (!paymentMethod) {
|
||||
return;
|
||||
}
|
||||
|
||||
request.taxInfo.country = taxInformation.country;
|
||||
request.taxInfo.postalCode = taxInformation.postalCode;
|
||||
request.taxInfo.taxId = taxInformation.taxId;
|
||||
request.taxInfo.line1 = taxInformation.line1;
|
||||
request.taxInfo.line2 = taxInformation.line2;
|
||||
request.taxInfo.city = taxInformation.city;
|
||||
request.taxInfo.state = taxInformation.state;
|
||||
|
||||
request.paymentSource = await this.paymentComponent.tokenize();
|
||||
request.paymentMethod = paymentMethod;
|
||||
request.billingAddress = getBillingAddressFromForm(this.formGroup.controls.billingAddress);
|
||||
|
||||
const provider = await this.providerApiService.postProviderSetup(this.providerId, request);
|
||||
|
||||
this.toastService.showToast({
|
||||
variant: "success",
|
||||
title: null,
|
||||
title: "",
|
||||
message: this.i18nService.t("providerSetup"),
|
||||
});
|
||||
|
||||
@@ -156,20 +149,10 @@ export class SetupComponent implements OnInit, OnDestroy {
|
||||
|
||||
await this.router.navigate(["/providers", provider.id]);
|
||||
} catch (e) {
|
||||
if (
|
||||
this.paymentComponent.selected === PaymentMethodType.PayPal &&
|
||||
typeof e === "string" &&
|
||||
e === "No payment method is available."
|
||||
) {
|
||||
this.toastService.showToast({
|
||||
variant: "error",
|
||||
title: null,
|
||||
message: this.i18nService.t("clickPayWithPayPal"),
|
||||
});
|
||||
} else {
|
||||
if (e !== null && typeof e === "object" && "message" in e && typeof e.message === "string") {
|
||||
e.message = this.i18nService.translate(e.message) || e.message;
|
||||
this.validationService.showError(e);
|
||||
}
|
||||
this.validationService.showError(e);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
<ng-container *ngIf="loading">
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin tw-text-muted"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="tw-sr-only">{{ "loading" | i18n }}</span>
|
||||
</ng-container>
|
||||
<bit-table *ngIf="!loading">
|
||||
<ng-container header>
|
||||
<tr>
|
||||
<th bitCell>{{ "date" | i18n }}</th>
|
||||
<th bitCell>{{ "invoice" | i18n }}</th>
|
||||
<th bitCell>{{ "total" | i18n }}</th>
|
||||
<th bitCell>{{ "status" | i18n }}</th>
|
||||
<th bitCell>{{ "clientDetails" | i18n }}</th>
|
||||
</tr>
|
||||
</ng-container>
|
||||
<ng-template body>
|
||||
<tr bitRow *ngFor="let invoice of invoices">
|
||||
<td bitCell>{{ invoice.date | date: "mediumDate" }}</td>
|
||||
<td bitCell>
|
||||
<a
|
||||
href="{{ invoice.url }}"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
title="{{ 'viewInvoice' | i18n }}"
|
||||
>
|
||||
{{ invoice.number }}
|
||||
</a>
|
||||
</td>
|
||||
<td bitCell>{{ invoice.total | currency: "$" }}</td>
|
||||
<td bitCell *ngIf="expandInvoiceStatus(invoice) as expandedInvoiceStatus">
|
||||
<span *ngIf="expandedInvoiceStatus === 'open'">
|
||||
{{ "open" | i18n | titlecase }}
|
||||
</span>
|
||||
<span *ngIf="expandedInvoiceStatus === 'unpaid'">
|
||||
<i class="bwi bwi-error tw-text-muted" aria-hidden="true"></i>
|
||||
{{ "unpaid" | i18n | titlecase }}
|
||||
</span>
|
||||
<span *ngIf="expandedInvoiceStatus === 'paid'">
|
||||
<i class="bwi bwi-check tw-text-success" aria-hidden="true"></i>
|
||||
{{ "paid" | i18n | titlecase }}
|
||||
</span>
|
||||
<span *ngIf="expandedInvoiceStatus === 'uncollectible'">
|
||||
<i class="bwi bwi-error tw-text-muted" aria-hidden="true"></i>
|
||||
{{ "uncollectible" | i18n | titlecase }}
|
||||
</span>
|
||||
</td>
|
||||
<td bitCell>
|
||||
<button type="button" bitLink (click)="runExport(invoice.id)">
|
||||
<span class="tw-font-normal">{{ "downloadCSV" | i18n }}</span>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</ng-template>
|
||||
</bit-table>
|
||||
<div *ngIf="!invoices || invoices.length === 0" class="tw-mt-10">
|
||||
<app-no-invoices></app-no-invoices>
|
||||
</div>
|
||||
@@ -0,0 +1,67 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { Component, Input, OnInit } from "@angular/core";
|
||||
|
||||
import {
|
||||
InvoiceResponse,
|
||||
InvoicesResponse,
|
||||
} from "@bitwarden/common/billing/models/response/invoices.response";
|
||||
import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service";
|
||||
|
||||
@Component({
|
||||
selector: "app-invoices",
|
||||
templateUrl: "./invoices.component.html",
|
||||
standalone: false,
|
||||
})
|
||||
export class InvoicesComponent implements OnInit {
|
||||
@Input() startWith?: InvoicesResponse;
|
||||
@Input() getInvoices?: () => Promise<InvoicesResponse>;
|
||||
@Input() getClientInvoiceReport?: (invoiceId: string) => Promise<string>;
|
||||
@Input() getClientInvoiceReportName?: (invoiceResponse: InvoiceResponse) => string;
|
||||
|
||||
protected invoices: InvoiceResponse[] = [];
|
||||
protected loading = true;
|
||||
|
||||
constructor(private fileDownloadService: FileDownloadService) {}
|
||||
|
||||
runExport = async (invoiceId: string): Promise<void> => {
|
||||
const blobData = await this.getClientInvoiceReport(invoiceId);
|
||||
let fileName = "report.csv";
|
||||
if (this.getClientInvoiceReportName) {
|
||||
const invoice = this.invoices.find((invoice) => invoice.id === invoiceId);
|
||||
fileName = this.getClientInvoiceReportName(invoice);
|
||||
}
|
||||
this.fileDownloadService.download({
|
||||
fileName,
|
||||
blobData,
|
||||
blobOptions: {
|
||||
type: "text/csv",
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
async ngOnInit(): Promise<void> {
|
||||
if (this.startWith) {
|
||||
this.invoices = this.startWith.invoices;
|
||||
} else if (this.getInvoices) {
|
||||
const response = await this.getInvoices();
|
||||
this.invoices = response.invoices;
|
||||
}
|
||||
this.loading = false;
|
||||
}
|
||||
|
||||
expandInvoiceStatus = (
|
||||
invoice: InvoiceResponse,
|
||||
): "open" | "unpaid" | "paid" | "uncollectible" => {
|
||||
switch (invoice.status) {
|
||||
case "open": {
|
||||
const dueDate = new Date(invoice.dueDate);
|
||||
return dueDate < new Date() ? "unpaid" : invoice.status;
|
||||
}
|
||||
case "paid":
|
||||
case "uncollectible": {
|
||||
return invoice.status;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import { Component } from "@angular/core";
|
||||
|
||||
import { CreditCardIcon } from "@bitwarden/assets/svg";
|
||||
|
||||
@Component({
|
||||
selector: "app-no-invoices",
|
||||
template: `<bit-no-items [icon]="icon">
|
||||
<div slot="title">{{ "noInvoicesToList" | i18n }}</div>
|
||||
</bit-no-items>`,
|
||||
standalone: false,
|
||||
})
|
||||
export class NoInvoicesComponent {
|
||||
icon = CreditCardIcon;
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
export * from "./billing-history/invoices.component";
|
||||
export * from "./billing-history/no-invoices.component";
|
||||
export * from "./billing-history/provider-billing-history.component";
|
||||
export * from "./clients";
|
||||
export * from "./guards/has-consolidated-billing.guard";
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
import { Component, OnDestroy, OnInit } from "@angular/core";
|
||||
import { ActivatedRoute, Router } from "@angular/router";
|
||||
import { ActivatedRoute } from "@angular/router";
|
||||
import {
|
||||
BehaviorSubject,
|
||||
combineLatest,
|
||||
EMPTY,
|
||||
filter,
|
||||
firstValueFrom,
|
||||
from,
|
||||
map,
|
||||
merge,
|
||||
Observable,
|
||||
of,
|
||||
@@ -19,7 +16,6 @@ import {
|
||||
tap,
|
||||
withLatestFrom,
|
||||
} from "rxjs";
|
||||
import { catchError } from "rxjs/operators";
|
||||
|
||||
import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service";
|
||||
import { Provider } from "@bitwarden/common/admin-console/models/domain/provider";
|
||||
@@ -49,13 +45,6 @@ import { SharedModule } from "@bitwarden/web-vault/app/shared";
|
||||
|
||||
import { ProviderWarningsService } from "../warnings/services";
|
||||
|
||||
class RedirectError {
|
||||
constructor(
|
||||
public path: string[],
|
||||
public relativeTo: ActivatedRoute,
|
||||
) {}
|
||||
}
|
||||
|
||||
type View = {
|
||||
activeUserId: UserId;
|
||||
provider: BitwardenSubscriber;
|
||||
@@ -92,18 +81,6 @@ export class ProviderPaymentDetailsComponent implements OnInit, OnDestroy {
|
||||
);
|
||||
|
||||
private load$: Observable<View> = this.provider$.pipe(
|
||||
switchMap((provider) =>
|
||||
this.configService
|
||||
.getFeatureFlag$(FeatureFlag.PM21881_ManagePaymentDetailsOutsideCheckout)
|
||||
.pipe(
|
||||
map((managePaymentDetailsOutsideCheckout) => {
|
||||
if (!managePaymentDetailsOutsideCheckout) {
|
||||
throw new RedirectError(["../subscription"], this.activatedRoute);
|
||||
}
|
||||
return provider;
|
||||
}),
|
||||
),
|
||||
),
|
||||
mapProviderToSubscriber,
|
||||
switchMap(async (provider) => {
|
||||
const getTaxIdWarning = firstValueFrom(
|
||||
@@ -131,14 +108,6 @@ export class ProviderPaymentDetailsComponent implements OnInit, OnDestroy {
|
||||
};
|
||||
}),
|
||||
shareReplay({ bufferSize: 1, refCount: false }),
|
||||
catchError((error: unknown) => {
|
||||
if (error instanceof RedirectError) {
|
||||
return from(this.router.navigate(error.path, { relativeTo: error.relativeTo })).pipe(
|
||||
switchMap(() => EMPTY),
|
||||
);
|
||||
}
|
||||
throw error;
|
||||
}),
|
||||
);
|
||||
|
||||
view$: Observable<View> = merge(
|
||||
@@ -158,7 +127,6 @@ export class ProviderPaymentDetailsComponent implements OnInit, OnDestroy {
|
||||
private messageListener: MessageListener,
|
||||
private providerService: ProviderService,
|
||||
private providerWarningsService: ProviderWarningsService,
|
||||
private router: Router,
|
||||
private subscriberBillingClient: SubscriberBillingClient,
|
||||
) {}
|
||||
|
||||
|
||||
@@ -62,51 +62,5 @@
|
||||
</bit-table>
|
||||
</div>
|
||||
</ng-container>
|
||||
@if (!managePaymentDetailsOutsideCheckout) {
|
||||
<!-- Account Credit -->
|
||||
<bit-section>
|
||||
<h2 bitTypography="h2">
|
||||
{{ "accountCredit" | i18n }}
|
||||
</h2>
|
||||
<p class="tw-text-lg tw-font-bold">{{ subscription.accountCredit | currency: "$" }}</p>
|
||||
<p bitTypography="body1">{{ "creditAppliedDesc" | i18n }}</p>
|
||||
</bit-section>
|
||||
<!-- Payment Method -->
|
||||
<bit-section>
|
||||
<h2 bitTypography="h2">{{ "paymentMethod" | i18n }}</h2>
|
||||
<p *ngIf="!subscription.paymentSource" bitTypography="body1">
|
||||
{{ "noPaymentMethod" | i18n }}
|
||||
</p>
|
||||
<ng-container *ngIf="subscription.paymentSource">
|
||||
<app-verify-bank-account
|
||||
*ngIf="subscription.paymentSource.needsVerification"
|
||||
[onSubmit]="verifyBankAccount"
|
||||
(submitted)="load()"
|
||||
>
|
||||
</app-verify-bank-account>
|
||||
<p>
|
||||
<i class="bwi bwi-fw" [ngClass]="paymentSourceClasses"></i>
|
||||
{{ subscription.paymentSource.description }}
|
||||
<span *ngIf="subscription.paymentSource.needsVerification"
|
||||
>- {{ "unverified" | i18n }}</span
|
||||
>
|
||||
</p>
|
||||
</ng-container>
|
||||
<button type="button" bitButton buttonType="secondary" [bitAction]="updatePaymentMethod">
|
||||
{{ updatePaymentSourceButtonText }}
|
||||
</button>
|
||||
</bit-section>
|
||||
<!-- Tax Information -->
|
||||
<bit-section>
|
||||
<h2 bitTypography="h2" class="tw-mt-16">{{ "taxInformation" | i18n }}</h2>
|
||||
<p>{{ "taxInformationDesc" | i18n }}</p>
|
||||
<app-manage-tax-information
|
||||
*ngIf="subscription.taxInformation"
|
||||
[startWith]="TaxInformation.from(subscription.taxInformation)"
|
||||
[onSubmit]="updateTaxInformation"
|
||||
(taxInformationUpdated)="load()"
|
||||
/>
|
||||
</bit-section>
|
||||
}
|
||||
</ng-container>
|
||||
</bit-container>
|
||||
|
||||
@@ -2,26 +2,14 @@
|
||||
// @ts-strict-ignore
|
||||
import { Component, OnDestroy, OnInit } from "@angular/core";
|
||||
import { ActivatedRoute } from "@angular/router";
|
||||
import { concatMap, lastValueFrom, Subject, takeUntil } from "rxjs";
|
||||
import { concatMap, Subject, takeUntil } from "rxjs";
|
||||
|
||||
import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/billing-api.service.abstraction";
|
||||
import { PaymentMethodType } 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 { VerifyBankAccountRequest } from "@bitwarden/common/billing/models/request/verify-bank-account.request";
|
||||
import {
|
||||
ProviderPlanResponse,
|
||||
ProviderSubscriptionResponse,
|
||||
} from "@bitwarden/common/billing/models/response/provider-subscription-response";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { DialogService, ToastService } from "@bitwarden/components";
|
||||
import { BillingNotificationService } from "@bitwarden/web-vault/app/billing/services/billing-notification.service";
|
||||
import {
|
||||
AdjustPaymentDialogComponent,
|
||||
AdjustPaymentDialogResultType,
|
||||
} from "@bitwarden/web-vault/app/billing/shared/adjust-payment-dialog/adjust-payment-dialog.component";
|
||||
|
||||
@Component({
|
||||
selector: "app-provider-subscription",
|
||||
@@ -36,18 +24,11 @@ export class ProviderSubscriptionComponent implements OnInit, OnDestroy {
|
||||
protected loading: boolean;
|
||||
private destroy$ = new Subject<void>();
|
||||
protected totalCost: number;
|
||||
protected managePaymentDetailsOutsideCheckout: boolean;
|
||||
|
||||
protected readonly TaxInformation = TaxInformation;
|
||||
|
||||
constructor(
|
||||
private billingApiService: BillingApiServiceAbstraction,
|
||||
private i18nService: I18nService,
|
||||
private route: ActivatedRoute,
|
||||
private billingNotificationService: BillingNotificationService,
|
||||
private dialogService: DialogService,
|
||||
private toastService: ToastService,
|
||||
private configService: ConfigService,
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
@@ -55,9 +36,6 @@ export class ProviderSubscriptionComponent implements OnInit, OnDestroy {
|
||||
.pipe(
|
||||
concatMap(async (params) => {
|
||||
this.providerId = params.providerId;
|
||||
this.managePaymentDetailsOutsideCheckout = await this.configService.getFeatureFlag(
|
||||
FeatureFlag.PM21881_ManagePaymentDetailsOutsideCheckout,
|
||||
);
|
||||
await this.load();
|
||||
this.firstLoaded = true;
|
||||
}),
|
||||
@@ -83,40 +61,6 @@ export class ProviderSubscriptionComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
protected updatePaymentMethod = async (): Promise<void> => {
|
||||
const dialogRef = AdjustPaymentDialogComponent.open(this.dialogService, {
|
||||
data: {
|
||||
initialPaymentMethod: this.subscription.paymentSource?.type,
|
||||
providerId: this.providerId,
|
||||
},
|
||||
});
|
||||
|
||||
const result = await lastValueFrom(dialogRef.closed);
|
||||
|
||||
if (result === AdjustPaymentDialogResultType.Submitted) {
|
||||
await this.load();
|
||||
}
|
||||
};
|
||||
|
||||
protected updateTaxInformation = async (taxInformation: TaxInformation) => {
|
||||
try {
|
||||
const request = ExpandedTaxInfoUpdateRequest.From(taxInformation);
|
||||
await this.billingApiService.updateProviderTaxInformation(this.providerId, request);
|
||||
this.billingNotificationService.showSuccess(this.i18nService.t("updatedTaxInformation"));
|
||||
} catch (error) {
|
||||
this.billingNotificationService.handleError(error);
|
||||
}
|
||||
};
|
||||
|
||||
protected verifyBankAccount = async (request: VerifyBankAccountRequest): Promise<void> => {
|
||||
await this.billingApiService.verifyProviderBankAccount(this.providerId, request);
|
||||
this.toastService.showToast({
|
||||
variant: "success",
|
||||
title: null,
|
||||
message: this.i18nService.t("verifiedBankAccount"),
|
||||
});
|
||||
};
|
||||
|
||||
protected getFormattedCost(
|
||||
cost: number,
|
||||
seatMinimum: number,
|
||||
@@ -161,7 +105,7 @@ export class ProviderSubscriptionComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
protected getBillingCadenceLabel(providerPlanResponse: ProviderPlanResponse): string {
|
||||
if (providerPlanResponse == null || providerPlanResponse == undefined) {
|
||||
if (providerPlanResponse == null) {
|
||||
return "month";
|
||||
}
|
||||
|
||||
@@ -174,27 +118,4 @@ export class ProviderSubscriptionComponent implements OnInit, OnDestroy {
|
||||
return "month";
|
||||
}
|
||||
}
|
||||
|
||||
protected get paymentSourceClasses() {
|
||||
if (this.subscription.paymentSource == null) {
|
||||
return [];
|
||||
}
|
||||
switch (this.subscription.paymentSource.type) {
|
||||
case PaymentMethodType.Card:
|
||||
return ["bwi-credit-card"];
|
||||
case PaymentMethodType.BankAccount:
|
||||
case PaymentMethodType.Check:
|
||||
return ["bwi-billing"];
|
||||
case PaymentMethodType.PayPal:
|
||||
return ["bwi-paypal text-primary"];
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
protected get updatePaymentSourceButtonText(): string {
|
||||
const key =
|
||||
this.subscription.paymentSource == null ? "addPaymentMethod" : "changePaymentMethod";
|
||||
return this.i18nService.t(key);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,8 +23,6 @@ import {
|
||||
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 { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
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";
|
||||
@@ -117,7 +115,6 @@ export class OverviewComponent implements OnInit, OnDestroy {
|
||||
private smOnboardingTasksService: SMOnboardingTasksService,
|
||||
private logService: LogService,
|
||||
private router: Router,
|
||||
private configService: ConfigService,
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
@@ -218,13 +215,12 @@ export class OverviewComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
async navigateToPaymentMethod() {
|
||||
const managePaymentDetailsOutsideCheckout = await this.configService.getFeatureFlag(
|
||||
FeatureFlag.PM21881_ManagePaymentDetailsOutsideCheckout,
|
||||
await this.router.navigate(
|
||||
["organizations", `${this.organizationId}`, "billing", "payment-details"],
|
||||
{
|
||||
state: { launchPaymentModalAutomatically: true },
|
||||
},
|
||||
);
|
||||
const route = managePaymentDetailsOutsideCheckout ? "payment-details" : "payment-method";
|
||||
await this.router.navigate(["organizations", `${this.organizationId}`, "billing", route], {
|
||||
state: { launchPaymentModalAutomatically: true },
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
|
||||
Reference in New Issue
Block a user