From 64daf5a889aac740bc626d9435ba6e692902e96d Mon Sep 17 00:00:00 2001 From: Alex Morask <144709477+amorask-bitwarden@users.noreply.github.com> Date: Thu, 1 May 2025 12:12:37 -0400 Subject: [PATCH] Require provider payment method during setup behind FF (#14550) --- .../shared/payment/payment.component.html | 2 +- .../shared/payment/payment.component.ts | 12 ++++ apps/web/src/locales/en/messages.json | 3 + .../providers/providers.module.ts | 2 + .../providers/setup/setup.component.html | 57 ++++++++++++++----- .../providers/setup/setup.component.ts | 17 +++++- .../provider/provider-setup.request.ts | 2 + libs/common/src/enums/feature-flag.enum.ts | 2 + 8 files changed, 80 insertions(+), 17 deletions(-) diff --git a/apps/web/src/app/billing/shared/payment/payment.component.html b/apps/web/src/app/billing/shared/payment/payment.component.html index c86975cd0e8..0d76d98e334 100644 --- a/apps/web/src/app/billing/shared/payment/payment.component.html +++ b/apps/web/src/app/billing/shared/payment/payment.component.html @@ -81,7 +81,7 @@ - {{ "verifyBankAccountWithStatementDescriptorWarning" | i18n }} + {{ bankAccountWarning }}
diff --git a/apps/web/src/app/billing/shared/payment/payment.component.ts b/apps/web/src/app/billing/shared/payment/payment.component.ts index c7c3e31c89f..5911e377869 100644 --- a/apps/web/src/app/billing/shared/payment/payment.component.ts +++ b/apps/web/src/app/billing/shared/payment/payment.component.ts @@ -8,6 +8,7 @@ import { takeUntil } from "rxjs/operators"; import { BillingApiServiceAbstraction } 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 { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { SharedModule } from "../../../shared"; import { BillingServicesModule, BraintreeService, StripeService } from "../../services"; @@ -37,6 +38,8 @@ export class PaymentComponent implements OnInit, OnDestroy { /** If provided, will be invoked with the tokenized payment source during form submission. */ @Input() protected onSubmit?: (request: TokenizedPaymentSourceRequest) => Promise; + @Input() private bankAccountWarningOverride?: string; + @Output() submitted = new EventEmitter(); private destroy$ = new Subject(); @@ -56,6 +59,7 @@ export class PaymentComponent implements OnInit, OnDestroy { constructor( private billingApiService: BillingApiServiceAbstraction, private braintreeService: BraintreeService, + private i18nService: I18nService, private stripeService: StripeService, ) {} @@ -200,4 +204,12 @@ export class PaymentComponent implements OnInit, OnDestroy { private get usingStripe(): boolean { return this.usingBankAccount || this.usingCard; } + + get bankAccountWarning(): string { + if (this.bankAccountWarningOverride) { + return this.bankAccountWarningOverride; + } else { + return this.i18nService.t("verifyBankAccountWithStatementDescriptorWarning"); + } + } } diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index da71d1cc219..ce45b538bbe 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -10631,5 +10631,8 @@ }, "restart": { "message": "Restart" + }, + "verifyProviderBankAccountWithStatementDescriptorWarning": { + "message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make a micro-deposit within the next 1-2 business days. Enter the statement descriptor code from this deposit on the provider's subscription page to verify the bank account. Failure to verify the bank account will result in a missed payment and your subscription being suspended." } } diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/providers.module.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/providers.module.ts index dd9baa99948..1c15812edc8 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/providers.module.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/providers.module.ts @@ -7,6 +7,7 @@ import { JslibModule } from "@bitwarden/angular/jslib.module"; import { CardComponent, 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 { OssModule } from "@bitwarden/web-vault/app/oss.module"; @@ -53,6 +54,7 @@ import { VerifyRecoverDeleteProviderComponent } from "./verify-recover-delete-pr ScrollingModule, VerifyBankAccountComponent, CardComponent, + PaymentComponent, ], declarations: [ AcceptProviderComponent, diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup.component.html b/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup.component.html index 38b4c3bc9de..4c5a35ea58d 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup.component.html +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup.component.html @@ -12,23 +12,50 @@

{{ "setupProviderDesc" | i18n }}

-

{{ "generalInformation" | i18n }}

-
-
- - {{ "providerName" | i18n }} - - + @if (!(requireProviderPaymentMethodDuringSetup$ | async)) { +

{{ "generalInformation" | i18n }}

+
+
+ + {{ "providerName" | i18n }} + + +
+
+ + {{ "billingEmail" | i18n }} + + {{ "providerBillingEmailHint" | i18n }} + +
-
- - {{ "billingEmail" | i18n }} - - {{ "providerBillingEmailHint" | i18n }} - + + } @else { +

{{ "billingInformation" | i18n }}

+
+
+ + {{ "providerName" | i18n }} + + +
+
+ + {{ "billingEmail" | i18n }} + + {{ "providerBillingEmailHint" | i18n }} + +
-
- +

{{ "paymentMethod" | i18n }}

+ + + } diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup.component.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup.component.ts index ecf649b8f31..0b6483b9f48 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup.component.ts @@ -3,13 +3,14 @@ import { Component, OnDestroy, OnInit, ViewChild } from "@angular/core"; import { FormBuilder, Validators } from "@angular/forms"; import { ActivatedRoute, Router } from "@angular/router"; -import { Subject, switchMap } from "rxjs"; +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 { ExpandedTaxInfoUpdateRequest } from "@bitwarden/common/billing/models/request/expanded-tax-info-update.request"; +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 { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service"; @@ -17,12 +18,14 @@ 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"; @Component({ selector: "provider-setup", templateUrl: "setup.component.html", }) export class SetupComponent implements OnInit, OnDestroy { + @ViewChild(PaymentComponent) paymentComponent: PaymentComponent; @ViewChild(ManageTaxInformationComponent) taxInformationComponent: ManageTaxInformationComponent; loading = true; @@ -36,6 +39,10 @@ export class SetupComponent implements OnInit, OnDestroy { private destroy$ = new Subject(); + requireProviderPaymentMethodDuringSetup$ = this.configService.getFeatureFlag$( + FeatureFlag.PM19956_RequireProviderPaymentMethodDuringSetup, + ); + constructor( private router: Router, private i18nService: I18nService, @@ -134,6 +141,14 @@ export class SetupComponent implements OnInit, OnDestroy { request.taxInfo.city = taxInformation.city; request.taxInfo.state = taxInformation.state; + const requireProviderPaymentMethodDuringSetup = await firstValueFrom( + this.requireProviderPaymentMethodDuringSetup$, + ); + + if (requireProviderPaymentMethodDuringSetup) { + request.paymentSource = await this.paymentComponent.tokenize(); + } + const provider = await this.providerApiService.postProviderSetup(this.providerId, request); this.toastService.showToast({ diff --git a/libs/common/src/admin-console/models/request/provider/provider-setup.request.ts b/libs/common/src/admin-console/models/request/provider/provider-setup.request.ts index d4992e969dc..5c9ea5526a0 100644 --- a/libs/common/src/admin-console/models/request/provider/provider-setup.request.ts +++ b/libs/common/src/admin-console/models/request/provider/provider-setup.request.ts @@ -1,6 +1,7 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore import { ExpandedTaxInfoUpdateRequest } from "../../../../billing/models/request/expanded-tax-info-update.request"; +import { TokenizedPaymentSourceRequest } from "../../../../billing/models/request/tokenized-payment-source.request"; export class ProviderSetupRequest { name: string; @@ -9,4 +10,5 @@ export class ProviderSetupRequest { token: string; key: string; taxInfo: ExpandedTaxInfoUpdateRequest; + paymentSource?: TokenizedPaymentSourceRequest; } diff --git a/libs/common/src/enums/feature-flag.enum.ts b/libs/common/src/enums/feature-flag.enum.ts index 0e0956b0460..3644ceefa9a 100644 --- a/libs/common/src/enums/feature-flag.enum.ts +++ b/libs/common/src/enums/feature-flag.enum.ts @@ -34,6 +34,7 @@ export enum FeatureFlag { PM12276_BreadcrumbEventLogs = "pm-12276-breadcrumbing-for-business-features", PM18794_ProviderPaymentMethod = "pm-18794-provider-payment-method", PM17772_AdminInitiatedSponsorships = "pm-17772-admin-initiated-sponsorships", + PM19956_RequireProviderPaymentMethodDuringSetup = "pm-19956-require-provider-payment-method-during-setup", /* Data Insights and Reporting */ CriticalApps = "pm-14466-risk-insights-critical-application", @@ -121,6 +122,7 @@ export const DefaultFeatureFlagValue = { [FeatureFlag.PM12276_BreadcrumbEventLogs]: FALSE, [FeatureFlag.PM18794_ProviderPaymentMethod]: FALSE, [FeatureFlag.PM17772_AdminInitiatedSponsorships]: FALSE, + [FeatureFlag.PM19956_RequireProviderPaymentMethodDuringSetup]: FALSE, /* Key Management */ [FeatureFlag.PrivateKeyRegeneration]: FALSE,