@ -4,34 +4,41 @@ import { Component, ViewChild } from "@angular/core";
import { takeUntilDestroyed } from "@angular/core/rxjs-interop" ;
import { FormControl , FormGroup , Validators } from "@angular/forms" ;
import { ActivatedRoute , Router } from "@angular/router" ;
import { combineLatest , concatMap , from , Observable, of , switchMap } from "rxjs" ;
import { combineLatest , concatMap , from , map, Observable, of , startWith , switchMap } from "rxjs" ;
import { debounceTime } from "rxjs/operators" ;
import { ApiService } from "@bitwarden/common/abstractions/api.service" ;
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service" ;
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions" ;
import { PaymentMethodType } from "@bitwarden/common/billing/enums" ;
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service" ;
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service" ;
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service" ;
import { SyncService } from "@bitwarden/common/platform/sync" ;
import { ToastService } from "@bitwarden/components" ;
import { TaxClient } from "@bitwarden/web-vault/app/billing/clients" ;
import { SubscriberBillingClient, TaxClient } from "@bitwarden/web-vault/app/billing/clients" ;
import {
EnterBillingAddressComponent ,
EnterPaymentMethodComponent ,
getBillingAddressFromForm ,
} from "@bitwarden/web-vault/app/billing/payment/components" ;
import { tokenizablePaymentMethodToLegacyEnum } from "@bitwarden/web-vault/app/billing/payment/types" ;
import {
tokenizablePaymentMethodToLegacyEnum ,
NonTokenizablePaymentMethods ,
} from "@bitwarden/web-vault/app/billing/payment/types" ;
import { mapAccountToSubscriber } from "@bitwarden/web-vault/app/billing/types" ;
@Component ( {
templateUrl : "./premium.component.html" ,
standalone : false ,
providers : [ TaxClient] ,
providers : [ SubscriberBillingClient, TaxClient] ,
} )
export class PremiumComponent {
@ViewChild ( EnterPaymentMethodComponent ) enterPaymentMethodComponent ! : EnterPaymentMethodComponent ;
protected hasPremiumFromAnyOrganization$ : Observable < boolean > ;
protected accountCredit$ : Observable < number > ;
protected hasEnoughAccountCredit$ : Observable < boolean > ;
protected formGroup = new FormGroup ( {
additionalStorage : new FormControl < number > ( 0 , [ Validators . min ( 0 ) , Validators . max ( 99 ) ] ) ,
@ -58,6 +65,7 @@ export class PremiumComponent {
private syncService : SyncService ,
private toastService : ToastService ,
private accountService : AccountService ,
private subscriberBillingClient : SubscriberBillingClient ,
private taxClient : TaxClient ,
) {
this . isSelfHost = this . platformUtilsService . isSelfHost ( ) ;
@ -68,6 +76,26 @@ export class PremiumComponent {
) ,
) ;
// Fetch account credit
this . accountCredit $ = this . accountService . activeAccount $ . pipe (
mapAccountToSubscriber ,
switchMap ( ( account ) = > this . subscriberBillingClient . getCredit ( account ) ) ,
) ;
// Check if user has enough account credit for the purchase
this . hasEnoughAccountCredit $ = combineLatest ( [
this . accountCredit $ ,
this . formGroup . valueChanges . pipe ( startWith ( this . formGroup . value ) ) ,
] ) . pipe (
map ( ( [ credit , formValue ] ) = > {
const selectedPaymentType = formValue . paymentMethod ? . type ;
if ( selectedPaymentType !== NonTokenizablePaymentMethods . accountCredit ) {
return true ; // Not using account credit, so this check doesn't apply
}
return credit >= this . total ;
} ) ,
) ;
combineLatest ( [
this . accountService . activeAccount $ . pipe (
switchMap ( ( account ) = >
@ -120,13 +148,26 @@ export class PremiumComponent {
return ;
}
const paymentMethod = await this . enterPaymentMethodComponent . tokenize ( ) ;
const legacyEnum = tokenizablePaymentMethodToLegacyEnum ( paymentMethod . type ) ;
// Check if account credit is selected
const selectedPaymentType = this . formGroup . value . paymentMethod . type ;
let paymentMethodType : number ;
let paymentToken : string ;
if ( selectedPaymentType === NonTokenizablePaymentMethods . accountCredit ) {
// Account credit doesn't need tokenization
paymentMethodType = PaymentMethodType . Credit ;
paymentToken = "" ;
} else {
// Tokenize for card, bank account, or PayPal
const paymentMethod = await this . enterPaymentMethodComponent . tokenize ( ) ;
paymentMethodType = tokenizablePaymentMethodToLegacyEnum ( paymentMethod . type ) ;
paymentToken = paymentMethod . token ;
}
const formData = new FormData ( ) ;
formData . append ( "paymentMethodType" , legacyEnum . toString ( ) ) ;
formData . append ( "paymentToken" , paymentMethod . token ) ;
formData . append ( "paymentMethodType" , paymentMethodType . toString ( ) ) ;
formData . append ( "paymentToken" , payment T oken) ;
formData . append ( "additionalStorageGb" , this . formGroup . value . additionalStorage . toString ( ) ) ;
formData . append ( "country" , this . formGroup . value . billingAddress . country ) ;
formData . append ( "postalCode" , this . formGroup . value . billingAddress . postalCode ) ;