1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-17 00:33:44 +00:00

[AC-2959] ACH Direct Debit POC (#10746)

* (No Logic) Fix typo in billing-api-service.abstraction file name

* (Cleanup) Remove payment method components and API methods from provider portal

Product team decided not to have a payment method page in the provider portal for consolidated billing. This just removes all the unused components and API methods.

* Add organization endpoints to support new payment method behavior

* Add payment-v2.component

This component existed in the libs folder because we used it for the provider portal, but since we've removed payment functionality from the provider portal, I moved it into web in this commit.

* (No Logic) Move existing payment.component into new payment component folder

* Add verify-bank-account.component

This component existed in the libs folder because we used it for the provider portal, but since we've removed payment functionality from the provider portal, I moved it into web in this commit.

* Add adjust-payment-dialog-v2.component

* (No Logic) Move existing adjust-payment-dialog.component into new adjust-payment-dialog component folder

* Add organization-payment-method.component

* Add feature flag: AC-2476-deprecate-stripe-sources-api

* Pivot organization payment method route on new feature flag

* Fix broken test
This commit is contained in:
Alex Morask
2024-08-28 10:48:22 -04:00
committed by GitHub
parent b0ffac04af
commit a58642e370
47 changed files with 623 additions and 481 deletions

View File

@@ -16,8 +16,6 @@ import {
ManageClientsComponent,
ManageClientSubscriptionDialogComponent,
ProviderBillingHistoryComponent,
ProviderPaymentMethodComponent,
ProviderSelectPaymentMethodDialogComponent,
ProviderSubscriptionComponent,
ProviderSubscriptionStatusComponent,
} from "../../billing/providers";
@@ -80,8 +78,6 @@ import { SetupComponent } from "./setup/setup.component";
ManageClientSubscriptionDialogComponent,
ProviderBillingHistoryComponent,
ProviderSubscriptionComponent,
ProviderSelectPaymentMethodDialogComponent,
ProviderPaymentMethodComponent,
ProviderSubscriptionStatusComponent,
],
providers: [WebProviderService],

View File

@@ -3,7 +3,7 @@ import { Injectable } from "@angular/core";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { OrganizationKeysRequest } from "@bitwarden/common/admin-console/models/request/organization-keys.request";
import { ProviderAddOrganizationRequest } from "@bitwarden/common/admin-console/models/request/provider/provider-add-organization.request";
import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/billilng-api.service.abstraction";
import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/billing-api.service.abstraction";
import { PlanType } from "@bitwarden/common/billing/enums";
import { CreateClientOrganizationRequest } from "@bitwarden/common/billing/models/request/create-client-organization.request";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";

View File

@@ -2,7 +2,7 @@ import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog";
import { Component, Inject, OnInit } from "@angular/core";
import { FormControl, FormGroup, Validators } from "@angular/forms";
import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/billilng-api.service.abstraction";
import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/billing-api.service.abstraction";
import { PlanType } from "@bitwarden/common/billing/enums";
import { PlanResponse } from "@bitwarden/common/billing/models/response/plan.response";
import { ProviderPlanResponse } from "@bitwarden/common/billing/models/response/provider-subscription-response";

View File

@@ -2,7 +2,7 @@ import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog";
import { Component, Inject } from "@angular/core";
import { FormBuilder, Validators } from "@angular/forms";
import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/billilng-api.service.abstraction";
import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/billing-api.service.abstraction";
import { UpdateClientOrganizationRequest } from "@bitwarden/common/billing/models/request/update-client-organization.request";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { DialogService, ToastService } from "@bitwarden/components";

View File

@@ -9,7 +9,7 @@ import { ProviderService } from "@bitwarden/common/admin-console/abstractions/pr
import { ProviderUserType } from "@bitwarden/common/admin-console/enums";
import { Provider } from "@bitwarden/common/admin-console/models/domain/provider";
import { ProviderOrganizationOrganizationDetailsResponse } from "@bitwarden/common/admin-console/models/response/provider/provider-organization.response";
import { BillingApiServiceAbstraction as BillingApiService } from "@bitwarden/common/billing/abstractions/billilng-api.service.abstraction";
import { BillingApiServiceAbstraction as BillingApiService } from "@bitwarden/common/billing/abstractions/billing-api.service.abstraction";
import { hasConsolidatedBilling } from "@bitwarden/common/billing/abstractions/provider-billing.service.abstraction";
import { PlanResponse } from "@bitwarden/common/billing/models/response/plan.response";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";

View File

@@ -1,7 +1,5 @@
export * from "./billing-history/provider-billing-history.component";
export * from "./clients";
export * from "./guards/has-consolidated-billing.guard";
export * from "./payment-method/provider-select-payment-method-dialog.component";
export * from "./payment-method/provider-payment-method.component";
export * from "./subscription/provider-subscription.component";
export * from "./subscription/provider-subscription-status.component";

View File

@@ -1,52 +0,0 @@
<app-header></app-header>
<ng-container *ngIf="loading">
<i
class="bwi bwi-spinner bwi-spin text-muted"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
></i>
<span class="tw-sr-only">{{ "loading" | i18n }}</span>
</ng-container>
<bit-container *ngIf="!loading">
<!-- Account Credit -->
<ng-container>
<h2 bitTypography="h2">
{{ "accountCredit" | i18n }}
</h2>
<p class="tw-text-lg tw-font-bold">{{ accountCredit | currency: "$" }}</p>
<p bitTypography="body1">{{ "creditAppliedDesc" | i18n }}</p>
<button type="button" bitButton buttonType="secondary" [bitAction]="addAccountCredit">
{{ "addCredit" | i18n }}
</button>
</ng-container>
<!-- Payment Method -->
<ng-container>
<h2 class="spaced-header">{{ "paymentMethod" | i18n }}</h2>
<p *ngIf="!hasPaymentMethod">{{ "noPaymentMethod" | i18n }}</p>
<app-verify-bank-account
[onSubmit]="verifyBankAccount"
(verificationSubmitted)="onDataUpdated()"
*ngIf="hasUnverifiedPaymentMethod"
/>
<ng-container *ngIf="hasPaymentMethod">
<p>
<i class="bwi bwi-fw" [ngClass]="paymentMethodClass"></i>
{{ paymentMethodDescription }}
</p>
</ng-container>
<button type="button" bitButton buttonType="secondary" [bitAction]="changePaymentMethod">
{{ (hasPaymentMethod ? "changePaymentMethod" : "addPaymentMethod") | i18n }}
</button>
</ng-container>
<!-- Tax Information -->
<ng-container>
<h2 class="spaced-header">{{ "taxInformation" | i18n }}</h2>
<p>{{ "taxInformationDesc" | i18n }}</p>
<app-manage-tax-information
*ngIf="taxInformation"
[startWith]="taxInformation"
[onSubmit]="updateTaxInformation"
(taxInformationUpdated)="onDataUpdated()"
/>
</ng-container>
</bit-container>

View File

@@ -1,140 +0,0 @@
import { Component, OnDestroy, OnInit } from "@angular/core";
import { ActivatedRoute } from "@angular/router";
import { from, lastValueFrom, Subject, switchMap } from "rxjs";
import { takeUntil } from "rxjs/operators";
import { openAddAccountCreditDialog } from "@bitwarden/angular/billing/components";
import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions";
import { PaymentMethodType } from "@bitwarden/common/billing/enums";
import { MaskedPaymentMethod, 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 { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { DialogService, ToastService } from "@bitwarden/components";
import {
openProviderSelectPaymentMethodDialog,
ProviderSelectPaymentMethodDialogResultType,
} from "./provider-select-payment-method-dialog.component";
@Component({
selector: "app-provider-payment-method",
templateUrl: "./provider-payment-method.component.html",
})
export class ProviderPaymentMethodComponent implements OnInit, OnDestroy {
protected providerId: string;
protected loading: boolean;
protected accountCredit: number;
protected maskedPaymentMethod: MaskedPaymentMethod;
protected taxInformation: TaxInformation;
private destroy$ = new Subject<void>();
constructor(
private activatedRoute: ActivatedRoute,
private billingApiService: BillingApiServiceAbstraction,
private dialogService: DialogService,
private i18nService: I18nService,
private toastService: ToastService,
) {}
addAccountCredit = () =>
openAddAccountCreditDialog(this.dialogService, {
data: {
providerId: this.providerId,
},
});
changePaymentMethod = async () => {
const dialogRef = openProviderSelectPaymentMethodDialog(this.dialogService, {
data: {
providerId: this.providerId,
},
});
const result = await lastValueFrom(dialogRef.closed);
if (result == ProviderSelectPaymentMethodDialogResultType.Submitted) {
await this.load();
}
};
async load() {
this.loading = true;
const paymentInformation = await this.billingApiService.getProviderPaymentInformation(
this.providerId,
);
this.accountCredit = paymentInformation.accountCredit;
this.maskedPaymentMethod = MaskedPaymentMethod.from(paymentInformation.paymentMethod);
this.taxInformation = TaxInformation.from(paymentInformation.taxInformation);
this.loading = false;
}
onDataUpdated = async () => await this.load();
updateTaxInformation = async (taxInformation: TaxInformation) => {
const request = ExpandedTaxInfoUpdateRequest.From(taxInformation);
await this.billingApiService.updateProviderTaxInformation(this.providerId, request);
this.toastService.showToast({
variant: "success",
title: null,
message: this.i18nService.t("updatedTaxInformation"),
});
};
verifyBankAccount = async (amount1: number, amount2: number) => {
const request = new VerifyBankAccountRequest(amount1, amount2);
await this.billingApiService.verifyProviderBankAccount(this.providerId, request);
};
ngOnInit() {
this.activatedRoute.params
.pipe(
switchMap(({ providerId }) => {
this.providerId = providerId;
return from(this.load());
}),
takeUntil(this.destroy$),
)
.subscribe();
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
protected get hasPaymentMethod(): boolean {
return !!this.maskedPaymentMethod;
}
protected get hasUnverifiedPaymentMethod(): boolean {
return !!this.maskedPaymentMethod && this.maskedPaymentMethod.needsVerification;
}
protected get paymentMethodClass(): string[] {
switch (this.maskedPaymentMethod.type) {
case PaymentMethodType.Card:
return ["bwi-credit-card"];
case PaymentMethodType.BankAccount:
return ["bwi-bank"];
case PaymentMethodType.PayPal:
return ["bwi-paypal tw-text-primary"];
default:
return [];
}
}
protected get paymentMethodDescription(): string {
let description = this.maskedPaymentMethod.description;
if (this.maskedPaymentMethod.type === PaymentMethodType.BankAccount) {
if (this.hasUnverifiedPaymentMethod) {
description += " - " + this.i18nService.t("unverified");
} else {
description += " - " + this.i18nService.t("verified");
}
}
return description;
}
}

View File

@@ -1,18 +0,0 @@
<form [formGroup]="formGroup" [bitSubmit]="submit">
<bit-dialog dialogSize="large">
<span bitDialogTitle class="tw-font-semibold">
{{ "addPaymentMethod" | i18n }}
</span>
<ng-container bitDialogContent>
<app-select-payment-method [showAccountCredit]="false" />
</ng-container>
<ng-container bitDialogFooter>
<button bitButton bitFormButton buttonType="primary" type="submit">
{{ "submit" | i18n }}
</button>
<button bitButton buttonType="secondary" type="button" [bitDialogClose]="ResultType.Closed">
{{ "cancel" | i18n }}
</button>
</ng-container>
</bit-dialog>
</form>

View File

@@ -1,60 +0,0 @@
import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog";
import { Component, EventEmitter, Inject, Output, ViewChild } from "@angular/core";
import { FormGroup } from "@angular/forms";
import { SelectPaymentMethodComponent } from "@bitwarden/angular/billing/components";
import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions";
import { TokenizedPaymentMethodRequest } from "@bitwarden/common/billing/models/request/tokenized-payment-method.request";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { DialogService, ToastService } from "@bitwarden/components";
type ProviderSelectPaymentMethodDialogParams = {
providerId: string;
};
export enum ProviderSelectPaymentMethodDialogResultType {
Closed = "closed",
Submitted = "submitted",
}
export const openProviderSelectPaymentMethodDialog = (
dialogService: DialogService,
dialogConfig: DialogConfig<ProviderSelectPaymentMethodDialogParams>,
) =>
dialogService.open<
ProviderSelectPaymentMethodDialogResultType,
ProviderSelectPaymentMethodDialogParams
>(ProviderSelectPaymentMethodDialogComponent, dialogConfig);
@Component({
templateUrl: "provider-select-payment-method-dialog.component.html",
})
export class ProviderSelectPaymentMethodDialogComponent {
@ViewChild(SelectPaymentMethodComponent)
selectPaymentMethodComponent: SelectPaymentMethodComponent;
@Output() providerPaymentMethodUpdated = new EventEmitter();
protected readonly formGroup = new FormGroup({});
protected readonly ResultType = ProviderSelectPaymentMethodDialogResultType;
constructor(
private billingApiService: BillingApiServiceAbstraction,
@Inject(DIALOG_DATA) private dialogParams: ProviderSelectPaymentMethodDialogParams,
private dialogRef: DialogRef<ProviderSelectPaymentMethodDialogResultType>,
private i18nService: I18nService,
private toastService: ToastService,
) {}
submit = async () => {
const tokenizedPaymentMethod = await this.selectPaymentMethodComponent.tokenizePaymentMethod();
const request = TokenizedPaymentMethodRequest.From(tokenizedPaymentMethod);
await this.billingApiService.updateProviderPaymentMethod(this.dialogParams.providerId, request);
this.providerPaymentMethodUpdated.emit();
this.toastService.showToast({
variant: "success",
title: null,
message: this.i18nService.t("updatedPaymentMethod"),
});
this.dialogRef.close(this.ResultType.Submitted);
};
}

View File

@@ -2,7 +2,7 @@ import { Component, OnDestroy, OnInit } from "@angular/core";
import { ActivatedRoute } from "@angular/router";
import { Subject, concatMap, takeUntil } from "rxjs";
import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/billilng-api.service.abstraction";
import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/billing-api.service.abstraction";
import { TaxInformation } from "@bitwarden/common/billing/models/domain";
import { ExpandedTaxInfoUpdateRequest } from "@bitwarden/common/billing/models/request/expanded-tax-info-update.request";
import {