mirror of
https://github.com/bitwarden/browser
synced 2026-02-11 05:53:42 +00:00
refactor(billing): update premium org upgrade payment to display existing payment method
This commit is contained in:
@@ -14,13 +14,12 @@
|
||||
</div>
|
||||
<div class="tw-pb-8 !tw-mx-0">
|
||||
<h5 bitTypography="h5">{{ "paymentMethod" | i18n }}</h5>
|
||||
<app-enter-payment-method
|
||||
[showBankAccount]="true"
|
||||
[showAccountCredit]="false"
|
||||
[group]="formGroup.controls.paymentForm"
|
||||
[includeBillingAddress]="false"
|
||||
#paymentComponent
|
||||
></app-enter-payment-method>
|
||||
<app-display-payment-method
|
||||
[subscriber]="subscriber()"
|
||||
[paymentMethod]="paymentMethod()"
|
||||
[hideHeader]="true"
|
||||
>
|
||||
</app-display-payment-method>
|
||||
<h5 bitTypography="h5" class="tw-pt-4 tw-pb-2">{{ "billingAddress" | i18n }}</h5>
|
||||
<app-enter-billing-address
|
||||
[group]="formGroup.controls.billingAddress"
|
||||
@@ -40,7 +39,7 @@
|
||||
bitButton
|
||||
bitFormButton
|
||||
buttonType="primary"
|
||||
[disabled]="loading() || !isFormValid()"
|
||||
[disabled]="loading() || !formGroup.valid"
|
||||
type="submit"
|
||||
>
|
||||
{{ "upgrade" | i18n }}
|
||||
|
||||
@@ -23,9 +23,11 @@ import {
|
||||
Observable,
|
||||
from,
|
||||
defer,
|
||||
map,
|
||||
tap,
|
||||
} from "rxjs";
|
||||
|
||||
import { Account } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { Account, AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { SubscriptionPricingServiceAbstraction } from "@bitwarden/common/billing/abstractions/subscription-pricing.service.abstraction";
|
||||
import {
|
||||
BusinessSubscriptionPricingTier,
|
||||
@@ -41,11 +43,14 @@ import { LogService } from "@bitwarden/logging";
|
||||
import { Cart, CartSummaryComponent } from "@bitwarden/pricing";
|
||||
import { SharedModule } from "@bitwarden/web-vault/app/shared";
|
||||
|
||||
import { SubscriberBillingClient } from "../../../clients/subscriber-billing.client";
|
||||
import {
|
||||
EnterBillingAddressComponent,
|
||||
EnterPaymentMethodComponent,
|
||||
DisplayPaymentMethodComponent,
|
||||
getBillingAddressFromForm,
|
||||
} from "../../../payment/components";
|
||||
import { MaskedPaymentMethod } from "../../../payment/types";
|
||||
import { BitwardenSubscriber, mapAccountToSubscriber } from "../../../types";
|
||||
|
||||
import {
|
||||
PremiumOrgUpgradeService,
|
||||
@@ -75,7 +80,7 @@ export type PremiumOrgUpgradePaymentResult = {
|
||||
SharedModule,
|
||||
CartSummaryComponent,
|
||||
ButtonModule,
|
||||
EnterPaymentMethodComponent,
|
||||
DisplayPaymentMethodComponent,
|
||||
EnterBillingAddressComponent,
|
||||
],
|
||||
providers: [PremiumOrgUpgradeService],
|
||||
@@ -108,12 +113,10 @@ export class PremiumOrgUpgradePaymentComponent implements OnInit, AfterViewInit
|
||||
protected goBack = output<void>();
|
||||
protected complete = output<PremiumOrgUpgradePaymentResult>();
|
||||
|
||||
readonly paymentComponent = viewChild.required(EnterPaymentMethodComponent);
|
||||
readonly cartSummaryComponent = viewChild.required(CartSummaryComponent);
|
||||
|
||||
protected formGroup = new FormGroup({
|
||||
organizationName: new FormControl<string>("", [Validators.required]),
|
||||
paymentForm: EnterPaymentMethodComponent.getFormGroup(),
|
||||
billingAddress: EnterBillingAddressComponent.getFormGroup(),
|
||||
});
|
||||
|
||||
@@ -121,6 +124,10 @@ export class PremiumOrgUpgradePaymentComponent implements OnInit, AfterViewInit
|
||||
protected readonly loading = signal(true);
|
||||
protected readonly upgradeToMessage = signal("");
|
||||
|
||||
// Signals for payment method
|
||||
protected readonly paymentMethod = signal<MaskedPaymentMethod | null>(null);
|
||||
protected readonly subscriber = signal<BitwardenSubscriber | null>(null);
|
||||
|
||||
protected readonly planMembershipMessage = computed<string>(
|
||||
() => this.PLAN_MEMBERSHIP_MESSAGES[this.selectedPlanId()] ?? "",
|
||||
);
|
||||
@@ -144,6 +151,15 @@ export class PremiumOrgUpgradePaymentComponent implements OnInit, AfterViewInit
|
||||
initialValue: this.getEmptyInvoicePreview(),
|
||||
});
|
||||
|
||||
private readonly i18nService = inject(I18nService);
|
||||
private readonly subscriptionPricingService = inject(SubscriptionPricingServiceAbstraction);
|
||||
private readonly toastService = inject(ToastService);
|
||||
private readonly logService = inject(LogService);
|
||||
private readonly destroyRef = inject(DestroyRef);
|
||||
private readonly premiumOrgUpgradeService = inject(PremiumOrgUpgradeService);
|
||||
private readonly subscriberBillingClient = inject(SubscriberBillingClient);
|
||||
private readonly accountService = inject(AccountService);
|
||||
|
||||
// Cart Summary data
|
||||
protected readonly cart = computed<Cart>(() => {
|
||||
if (!this.selectedPlan()) {
|
||||
@@ -180,13 +196,6 @@ export class PremiumOrgUpgradePaymentComponent implements OnInit, AfterViewInit
|
||||
};
|
||||
});
|
||||
|
||||
private readonly i18nService = inject(I18nService);
|
||||
private readonly subscriptionPricingService = inject(SubscriptionPricingServiceAbstraction);
|
||||
private readonly toastService = inject(ToastService);
|
||||
private readonly logService = inject(LogService);
|
||||
private readonly destroyRef = inject(DestroyRef);
|
||||
private readonly premiumOrgUpgradeService = inject(PremiumOrgUpgradeService);
|
||||
|
||||
async ngOnInit(): Promise<void> {
|
||||
// If the selected plan is Personal Premium, no upgrade is needed
|
||||
if (this.selectedPlanId() == PersonalSubscriptionPricingTierIds.Premium) {
|
||||
@@ -231,6 +240,22 @@ export class PremiumOrgUpgradePaymentComponent implements OnInit, AfterViewInit
|
||||
}
|
||||
});
|
||||
|
||||
this.accountService.activeAccount$
|
||||
.pipe(
|
||||
mapAccountToSubscriber,
|
||||
switchMap((subscriber) =>
|
||||
from(this.subscriberBillingClient.getPaymentMethod(subscriber)).pipe(
|
||||
map((paymentMethod) => ({ subscriber, paymentMethod })),
|
||||
),
|
||||
),
|
||||
tap(({ subscriber, paymentMethod }) => {
|
||||
this.subscriber.set(subscriber);
|
||||
this.paymentMethod.set(paymentMethod);
|
||||
}),
|
||||
takeUntilDestroyed(this.destroyRef),
|
||||
)
|
||||
.subscribe();
|
||||
|
||||
this.loading.set(false);
|
||||
}
|
||||
|
||||
@@ -240,7 +265,7 @@ export class PremiumOrgUpgradePaymentComponent implements OnInit, AfterViewInit
|
||||
}
|
||||
|
||||
protected submit = async (): Promise<void> => {
|
||||
if (!this.isFormValid()) {
|
||||
if (!this.formGroup.valid) {
|
||||
this.formGroup.markAllAsTouched();
|
||||
return;
|
||||
}
|
||||
@@ -265,10 +290,6 @@ export class PremiumOrgUpgradePaymentComponent implements OnInit, AfterViewInit
|
||||
}
|
||||
};
|
||||
|
||||
protected isFormValid(): boolean {
|
||||
return this.formGroup.valid && this.paymentComponent().validate();
|
||||
}
|
||||
|
||||
private async processUpgrade(): Promise<PremiumOrgUpgradePaymentResult> {
|
||||
const billingAddress = getBillingAddressFromForm(this.formGroup.controls.billingAddress);
|
||||
const organizationName = this.formGroup.value?.organizationName;
|
||||
@@ -281,12 +302,6 @@ export class PremiumOrgUpgradePaymentComponent implements OnInit, AfterViewInit
|
||||
throw new Error("Organization name is required");
|
||||
}
|
||||
|
||||
const paymentMethod = await this.paymentComponent().tokenize();
|
||||
|
||||
if (!paymentMethod) {
|
||||
throw new Error("Payment method is required");
|
||||
}
|
||||
|
||||
const organizationId = await this.premiumOrgUpgradeService.upgradeToOrganization(
|
||||
this.account(),
|
||||
organizationName,
|
||||
|
||||
Reference in New Issue
Block a user