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:
192
apps/web/src/app/billing/shared/payment/payment-v2.component.ts
Normal file
192
apps/web/src/app/billing/shared/payment/payment-v2.component.ts
Normal file
@@ -0,0 +1,192 @@
|
||||
import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from "@angular/core";
|
||||
import { FormControl, FormGroup, Validators } from "@angular/forms";
|
||||
import { Subject } from "rxjs";
|
||||
import { takeUntil } from "rxjs/operators";
|
||||
|
||||
import {
|
||||
BillingApiServiceAbstraction,
|
||||
BraintreeServiceAbstraction,
|
||||
StripeServiceAbstraction,
|
||||
} from "@bitwarden/common/billing/abstractions";
|
||||
import { PaymentMethodType } from "@bitwarden/common/billing/enums";
|
||||
import { TokenizedPaymentSourceRequest } from "@bitwarden/common/billing/models/request/tokenized-payment-source.request";
|
||||
|
||||
import { SharedModule } from "../../../shared";
|
||||
|
||||
/**
|
||||
* Render a form that allows the user to enter their payment method, tokenize it against one of our payment providers and,
|
||||
* optionally, submit it using the {@link onSubmit} function if it is provided.
|
||||
*
|
||||
* This component is meant to replace the existing {@link PaymentComponent} which is using the deprecated Stripe Sources API.
|
||||
*/
|
||||
@Component({
|
||||
selector: "app-payment-v2",
|
||||
templateUrl: "./payment-v2.component.html",
|
||||
standalone: true,
|
||||
imports: [SharedModule],
|
||||
})
|
||||
export class PaymentV2Component implements OnInit, OnDestroy {
|
||||
/** Show account credit as a payment option. */
|
||||
@Input() showAccountCredit: boolean = true;
|
||||
/** Show bank account as a payment option. */
|
||||
@Input() showBankAccount: boolean = true;
|
||||
/** Show PayPal as a payment option. */
|
||||
@Input() showPayPal: boolean = true;
|
||||
|
||||
/** The payment method selected by default when the component renders. */
|
||||
@Input() private initialPaymentMethod: PaymentMethodType = PaymentMethodType.Card;
|
||||
/** If provided, will be invoked with the tokenized payment source during form submission. */
|
||||
@Input() protected onSubmit?: (request: TokenizedPaymentSourceRequest) => Promise<void>;
|
||||
|
||||
@Output() submitted = new EventEmitter<PaymentMethodType>();
|
||||
|
||||
private destroy$ = new Subject<void>();
|
||||
|
||||
protected formGroup = new FormGroup({
|
||||
paymentMethod: new FormControl<PaymentMethodType>(null),
|
||||
bankInformation: new FormGroup({
|
||||
routingNumber: new FormControl<string>("", [Validators.required]),
|
||||
accountNumber: new FormControl<string>("", [Validators.required]),
|
||||
accountHolderName: new FormControl<string>("", [Validators.required]),
|
||||
accountHolderType: new FormControl<string>("", [Validators.required]),
|
||||
}),
|
||||
});
|
||||
|
||||
protected PaymentMethodType = PaymentMethodType;
|
||||
|
||||
constructor(
|
||||
private billingApiService: BillingApiServiceAbstraction,
|
||||
private braintreeService: BraintreeServiceAbstraction,
|
||||
private stripeService: StripeServiceAbstraction,
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.formGroup.controls.paymentMethod.patchValue(this.initialPaymentMethod);
|
||||
|
||||
this.stripeService.loadStripe(
|
||||
{
|
||||
cardNumber: "#stripe-card-number",
|
||||
cardExpiry: "#stripe-card-expiry",
|
||||
cardCvc: "#stripe-card-cvc",
|
||||
},
|
||||
this.initialPaymentMethod === PaymentMethodType.Card,
|
||||
);
|
||||
|
||||
if (this.showPayPal) {
|
||||
this.braintreeService.loadBraintree(
|
||||
"#braintree-container",
|
||||
this.initialPaymentMethod === PaymentMethodType.PayPal,
|
||||
);
|
||||
}
|
||||
|
||||
this.formGroup
|
||||
.get("paymentMethod")
|
||||
.valueChanges.pipe(takeUntil(this.destroy$))
|
||||
.subscribe((type) => {
|
||||
this.onPaymentMethodChange(type);
|
||||
});
|
||||
}
|
||||
|
||||
/** Programmatically select the provided payment method. */
|
||||
select = (paymentMethod: PaymentMethodType) => {
|
||||
this.formGroup.value.paymentMethod = paymentMethod;
|
||||
};
|
||||
|
||||
protected submit = async () => {
|
||||
const { type, token } = await this.tokenize();
|
||||
await this.onSubmit({ type, token });
|
||||
this.submitted.emit(type);
|
||||
};
|
||||
|
||||
/**
|
||||
* Tokenize the payment method information entered by the user against one of our payment providers.
|
||||
*
|
||||
* - {@link PaymentMethodType.Card} => [Stripe.confirmCardSetup]{@link https://docs.stripe.com/js/setup_intents/confirm_card_setup}
|
||||
* - {@link PaymentMethodType.BankAccount} => [Stripe.confirmUsBankAccountSetup]{@link https://docs.stripe.com/js/setup_intents/confirm_us_bank_account_setup}
|
||||
* - {@link PaymentMethodType.PayPal} => [Braintree.requestPaymentMethod]{@link https://braintree.github.io/braintree-web-drop-in/docs/current/Dropin.html#requestPaymentMethod}
|
||||
* */
|
||||
async tokenize(): Promise<{ type: PaymentMethodType; token: string }> {
|
||||
const type = this.selected;
|
||||
|
||||
if (this.usingStripe) {
|
||||
const clientSecret = await this.billingApiService.createSetupIntent(type);
|
||||
|
||||
if (this.usingBankAccount) {
|
||||
const token = await this.stripeService.setupBankAccountPaymentMethod(clientSecret, {
|
||||
accountHolderName: this.formGroup.value.bankInformation.accountHolderName,
|
||||
routingNumber: this.formGroup.value.bankInformation.routingNumber,
|
||||
accountNumber: this.formGroup.value.bankInformation.accountNumber,
|
||||
accountHolderType: this.formGroup.value.bankInformation.accountHolderType,
|
||||
});
|
||||
return {
|
||||
type,
|
||||
token,
|
||||
};
|
||||
}
|
||||
|
||||
if (this.usingCard) {
|
||||
const token = await this.stripeService.setupCardPaymentMethod(clientSecret);
|
||||
return {
|
||||
type,
|
||||
token,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (this.usingPayPal) {
|
||||
const token = await this.braintreeService.requestPaymentMethod();
|
||||
return {
|
||||
type,
|
||||
token,
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
this.stripeService.unloadStripe();
|
||||
if (this.showPayPal) {
|
||||
this.braintreeService.unloadBraintree();
|
||||
}
|
||||
}
|
||||
|
||||
private onPaymentMethodChange(type: PaymentMethodType): void {
|
||||
switch (type) {
|
||||
case PaymentMethodType.Card: {
|
||||
this.stripeService.mountElements();
|
||||
break;
|
||||
}
|
||||
case PaymentMethodType.PayPal: {
|
||||
this.braintreeService.createDropin();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
get selected(): PaymentMethodType {
|
||||
return this.formGroup.value.paymentMethod;
|
||||
}
|
||||
|
||||
protected get usingAccountCredit(): boolean {
|
||||
return this.selected === PaymentMethodType.Credit;
|
||||
}
|
||||
|
||||
protected get usingBankAccount(): boolean {
|
||||
return this.selected === PaymentMethodType.BankAccount;
|
||||
}
|
||||
|
||||
protected get usingCard(): boolean {
|
||||
return this.selected === PaymentMethodType.Card;
|
||||
}
|
||||
|
||||
protected get usingPayPal(): boolean {
|
||||
return this.selected === PaymentMethodType.PayPal;
|
||||
}
|
||||
|
||||
private get usingStripe(): boolean {
|
||||
return this.usingBankAccount || this.usingCard;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user