mirror of
https://github.com/bitwarden/browser
synced 2025-12-16 08:13:42 +00:00
[EC-8] Repurpose payment method component
Update end user payment method component to be usable for organizations.
This commit is contained in:
@@ -5,12 +5,12 @@ import { AuthGuard } from "@bitwarden/angular/guards/auth.guard";
|
|||||||
import { Permissions } from "@bitwarden/common/enums/permissions";
|
import { Permissions } from "@bitwarden/common/enums/permissions";
|
||||||
|
|
||||||
import { OrganizationBillingTabComponent } from "../modules/organizations/billing/organization-billing-tab.component";
|
import { OrganizationBillingTabComponent } from "../modules/organizations/billing/organization-billing-tab.component";
|
||||||
import { OrganizationPaymentMethodComponent } from "../modules/organizations/billing/organization-payment-method.component";
|
|
||||||
import { OrganizationSubscriptionComponent } from "../modules/organizations/billing/organization-subscription.component";
|
import { OrganizationSubscriptionComponent } from "../modules/organizations/billing/organization-subscription.component";
|
||||||
import { ReportListComponent } from "../modules/organizations/reporting/report-list.component";
|
import { ReportListComponent } from "../modules/organizations/reporting/report-list.component";
|
||||||
import { ReportingComponent } from "../modules/organizations/reporting/reporting.component";
|
import { ReportingComponent } from "../modules/organizations/reporting/reporting.component";
|
||||||
import { OrganizationVaultModule } from "../modules/vault/modules/organization-vault/organization-vault.module";
|
import { OrganizationVaultModule } from "../modules/vault/modules/organization-vault/organization-vault.module";
|
||||||
import { BillingHistoryComponent } from "../settings/billing-history.component";
|
import { BillingHistoryComponent } from "../settings/billing-history.component";
|
||||||
|
import { PaymentMethodComponent } from "../settings/payment-method.component";
|
||||||
|
|
||||||
import { PermissionsGuard } from "./guards/permissions.guard";
|
import { PermissionsGuard } from "./guards/permissions.guard";
|
||||||
import { OrganizationLayoutComponent } from "./layouts/organization-layout.component";
|
import { OrganizationLayoutComponent } from "./layouts/organization-layout.component";
|
||||||
@@ -162,7 +162,7 @@ const routes: Routes = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "payment-method",
|
path: "payment-method",
|
||||||
component: OrganizationPaymentMethodComponent,
|
component: PaymentMethodComponent,
|
||||||
canActivate: [PermissionsGuard],
|
canActivate: [PermissionsGuard],
|
||||||
data: { titleId: "paymentMethod", permissions: [Permissions.ManageBilling] },
|
data: { titleId: "paymentMethod", permissions: [Permissions.ManageBilling] },
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<div class="tabbed-header d-flex">
|
<div class="d-flex" [ngClass]="headerClass">
|
||||||
<h1>
|
<h1>
|
||||||
{{ "paymentMethod" | i18n }}
|
{{ "paymentMethod" | i18n }}
|
||||||
</h1>
|
</h1>
|
||||||
@@ -40,18 +40,63 @@
|
|||||||
<h2 class="spaced-header">{{ "paymentMethod" | i18n }}</h2>
|
<h2 class="spaced-header">{{ "paymentMethod" | i18n }}</h2>
|
||||||
<p *ngIf="!paymentSource">{{ "noPaymentMethod" | i18n }}</p>
|
<p *ngIf="!paymentSource">{{ "noPaymentMethod" | i18n }}</p>
|
||||||
<ng-container *ngIf="paymentSource">
|
<ng-container *ngIf="paymentSource">
|
||||||
|
<app-callout type="warning" title="{{ 'verifyBankAccount' | i18n }}">
|
||||||
|
<p>{{ "verifyBankAccountDesc" | i18n }} {{ "verifyBankAccountFailureWarning" | i18n }}</p>
|
||||||
|
<form
|
||||||
|
#verifyForm
|
||||||
|
class="form-inline"
|
||||||
|
(ngSubmit)="verifyBank()"
|
||||||
|
[appApiAction]="verifyBankPromise"
|
||||||
|
ngNativeValidate
|
||||||
|
>
|
||||||
|
<label class="sr-only" for="verifyAmount1">{{ "amount" | i18n: "1" }}</label>
|
||||||
|
<div class="input-group mr-2">
|
||||||
|
<div class="input-group-prepend">
|
||||||
|
<div class="input-group-text">$0.</div>
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
class="form-control"
|
||||||
|
id="verifyAmount1"
|
||||||
|
placeholder="xx"
|
||||||
|
name="Amount1"
|
||||||
|
[(ngModel)]="verifyAmount1"
|
||||||
|
min="1"
|
||||||
|
max="99"
|
||||||
|
step="1"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<label class="sr-only" for="verifyAmount2">{{ "amount" | i18n: "2" }}</label>
|
||||||
|
<div class="input-group mr-2">
|
||||||
|
<div class="input-group-prepend">
|
||||||
|
<div class="input-group-text">$0.</div>
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
class="form-control"
|
||||||
|
id="verifyAmount2"
|
||||||
|
placeholder="xx"
|
||||||
|
name="Amount2"
|
||||||
|
[(ngModel)]="verifyAmount2"
|
||||||
|
min="1"
|
||||||
|
max="99"
|
||||||
|
step="1"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
class="btn btn-outline-primary btn-submit"
|
||||||
|
[disabled]="verifyForm.loading"
|
||||||
|
>
|
||||||
|
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
|
||||||
|
<span>{{ "verifyBankAccount" | i18n }}</span>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</app-callout>
|
||||||
<p>
|
<p>
|
||||||
<i
|
<i class="bwi bwi-fw" [ngClass]="paymentSourceClasses"></i>
|
||||||
class="bwi bwi-fw"
|
|
||||||
[ngClass]="{
|
|
||||||
'bwi-credit-card': paymentSource.type === paymentMethodType.Card,
|
|
||||||
'bwi-bank': paymentSource.type === paymentMethodType.BankAccount,
|
|
||||||
'bwi-money': paymentSource.type === paymentMethodType.Check,
|
|
||||||
'bwi-paypal text-primary': paymentSource.type === paymentMethodType.PayPal,
|
|
||||||
'bwi-apple text-muted': paymentSource.type === paymentMethodType.AppleInApp,
|
|
||||||
'bwi-google text-muted': paymentSource.type === paymentMethodType.GoogleInApp
|
|
||||||
}"
|
|
||||||
></i>
|
|
||||||
<span *ngIf="paymentSourceInApp">{{ "inAppPurchase" | i18n }}</span>
|
<span *ngIf="paymentSourceInApp">{{ "inAppPurchase" | i18n }}</span>
|
||||||
{{ paymentSource.description }}
|
{{ paymentSource.description }}
|
||||||
</p>
|
</p>
|
||||||
@@ -66,4 +111,29 @@
|
|||||||
*ngIf="showAdjustPayment"
|
*ngIf="showAdjustPayment"
|
||||||
>
|
>
|
||||||
</app-adjust-payment>
|
</app-adjust-payment>
|
||||||
|
<ng-container *ngIf="forOrganization">
|
||||||
|
<h2 class="spaced-header">{{ "taxInformation" | i18n }}</h2>
|
||||||
|
<p>{{ "taxInformationDesc" | i18n }}</p>
|
||||||
|
<div *ngIf="!org || loading">
|
||||||
|
<i
|
||||||
|
class="bwi bwi-spinner bwi-spin text-muted"
|
||||||
|
title="{{ 'loading' | i18n }}"
|
||||||
|
aria-hidden="true"
|
||||||
|
></i>
|
||||||
|
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||||
|
</div>
|
||||||
|
<form
|
||||||
|
*ngIf="org && !loading"
|
||||||
|
#formTax
|
||||||
|
(ngSubmit)="submitTaxInfo()"
|
||||||
|
[appApiAction]="taxFormPromise"
|
||||||
|
ngNativeValidate
|
||||||
|
>
|
||||||
|
<app-tax-info></app-tax-info>
|
||||||
|
<button type="submit" class="btn btn-primary btn-submit" [disabled]="formTax.loading">
|
||||||
|
<i class="bwi bwi-spinner bwi-spin" title="{{ 'loading' | i18n }}" aria-hidden="true"></i>
|
||||||
|
<span>{{ "save" | i18n }}</span>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</ng-container>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|||||||
@@ -1,37 +1,59 @@
|
|||||||
import { Component, OnInit } from "@angular/core";
|
import { Component, OnInit, ViewChild } from "@angular/core";
|
||||||
import { Router } from "@angular/router";
|
import { ActivatedRoute, Router } from "@angular/router";
|
||||||
|
|
||||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||||
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
||||||
|
import { LogService } from "@bitwarden/common/abstractions/log.service";
|
||||||
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
|
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
|
||||||
import { PaymentMethodType } from "@bitwarden/common/enums/paymentMethodType";
|
import { PaymentMethodType } from "@bitwarden/common/enums/paymentMethodType";
|
||||||
|
import { VerifyBankRequest } from "@bitwarden/common/models/request/verifyBankRequest";
|
||||||
import { BillingPaymentResponse } from "@bitwarden/common/models/response/billingPaymentResponse";
|
import { BillingPaymentResponse } from "@bitwarden/common/models/response/billingPaymentResponse";
|
||||||
|
import { OrganizationResponse } from "@bitwarden/common/models/response/organizationResponse";
|
||||||
|
|
||||||
|
import { TaxInfoComponent } from "./tax-info.component";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: "app-payment-method",
|
selector: "app-payment-method",
|
||||||
templateUrl: "payment-method.component.html",
|
templateUrl: "payment-method.component.html",
|
||||||
})
|
})
|
||||||
export class PaymentMethodComponent implements OnInit {
|
export class PaymentMethodComponent implements OnInit {
|
||||||
|
@ViewChild(TaxInfoComponent) taxInfo: TaxInfoComponent;
|
||||||
|
|
||||||
loading = false;
|
loading = false;
|
||||||
firstLoaded = false;
|
firstLoaded = false;
|
||||||
showAdjustPayment = false;
|
showAdjustPayment = false;
|
||||||
showAddCredit = false;
|
showAddCredit = false;
|
||||||
billing: BillingPaymentResponse;
|
billing: BillingPaymentResponse;
|
||||||
|
org: OrganizationResponse;
|
||||||
paymentMethodType = PaymentMethodType;
|
paymentMethodType = PaymentMethodType;
|
||||||
|
organizationId: string;
|
||||||
|
verifyAmount1: number;
|
||||||
|
verifyAmount2: number;
|
||||||
|
|
||||||
|
verifyBankPromise: Promise<any>;
|
||||||
|
taxFormPromise: Promise<any>;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
protected apiService: ApiService,
|
protected apiService: ApiService,
|
||||||
protected i18nService: I18nService,
|
protected i18nService: I18nService,
|
||||||
protected platformUtilsService: PlatformUtilsService,
|
protected platformUtilsService: PlatformUtilsService,
|
||||||
private router: Router
|
private router: Router,
|
||||||
|
private logService: LogService,
|
||||||
|
private route: ActivatedRoute
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
if (this.platformUtilsService.isSelfHost()) {
|
this.route.params.subscribe(async (params) => {
|
||||||
this.router.navigate(["/settings/subscription"]);
|
if (params.organizationId) {
|
||||||
}
|
this.organizationId = params.organizationId;
|
||||||
await this.load();
|
} else if (this.platformUtilsService.isSelfHost()) {
|
||||||
this.firstLoaded = true;
|
this.router.navigate(["/settings/subscription"]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.load();
|
||||||
|
this.firstLoaded = true;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async load() {
|
async load() {
|
||||||
@@ -39,7 +61,16 @@ export class PaymentMethodComponent implements OnInit {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
this.billing = await this.apiService.getUserBillingPayment();
|
|
||||||
|
if (this.forOrganization) {
|
||||||
|
const billingPromise = this.apiService.getOrganizationBilling(this.organizationId);
|
||||||
|
const orgPromise = this.apiService.getOrganization(this.organizationId);
|
||||||
|
|
||||||
|
[this.billing, this.org] = await Promise.all([billingPromise, orgPromise]);
|
||||||
|
} else {
|
||||||
|
this.billing = await this.apiService.getUserBillingPayment();
|
||||||
|
}
|
||||||
|
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,6 +116,37 @@ export class PaymentMethodComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async verifyBank() {
|
||||||
|
if (this.loading || !this.forOrganization) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const request = new VerifyBankRequest();
|
||||||
|
request.amount1 = this.verifyAmount1;
|
||||||
|
request.amount2 = this.verifyAmount2;
|
||||||
|
this.verifyBankPromise = this.apiService.postOrganizationVerifyBank(
|
||||||
|
this.organizationId,
|
||||||
|
request
|
||||||
|
);
|
||||||
|
await this.verifyBankPromise;
|
||||||
|
this.platformUtilsService.showToast(
|
||||||
|
"success",
|
||||||
|
null,
|
||||||
|
this.i18nService.t("verifiedBankAccount")
|
||||||
|
);
|
||||||
|
this.load();
|
||||||
|
} catch (e) {
|
||||||
|
this.logService.error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async submitTaxInfo() {
|
||||||
|
this.taxFormPromise = this.taxInfo.submitTaxInfo();
|
||||||
|
await this.taxFormPromise;
|
||||||
|
this.platformUtilsService.showToast("success", null, this.i18nService.t("taxInfoUpdated"));
|
||||||
|
}
|
||||||
|
|
||||||
get isCreditBalance() {
|
get isCreditBalance() {
|
||||||
return this.billing == null || this.billing.balance <= 0;
|
return this.billing == null || this.billing.balance <= 0;
|
||||||
}
|
}
|
||||||
@@ -97,6 +159,36 @@ export class PaymentMethodComponent implements OnInit {
|
|||||||
return this.billing != null ? this.billing.paymentSource : null;
|
return this.billing != null ? this.billing.paymentSource : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get forOrganization() {
|
||||||
|
return this.organizationId != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
get headerClass() {
|
||||||
|
return this.forOrganization ? ["page-header"] : ["tabbed-header"];
|
||||||
|
}
|
||||||
|
|
||||||
|
get paymentSourceClasses() {
|
||||||
|
if (this.paymentSource == null) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
switch (this.paymentSource.type) {
|
||||||
|
case PaymentMethodType.Card:
|
||||||
|
return ["bwi-credit-card"];
|
||||||
|
case PaymentMethodType.BankAccount:
|
||||||
|
return ["bwi-bank"];
|
||||||
|
case PaymentMethodType.Check:
|
||||||
|
return ["bwi-money"];
|
||||||
|
case PaymentMethodType.AppleInApp:
|
||||||
|
return ["bwi-apple text-muted"];
|
||||||
|
case PaymentMethodType.GoogleInApp:
|
||||||
|
return ["bwi-google text-muted"];
|
||||||
|
case PaymentMethodType.PayPal:
|
||||||
|
return ["bwi-paypal text-primary"];
|
||||||
|
default:
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
get paymentSourceInApp() {
|
get paymentSourceInApp() {
|
||||||
return (
|
return (
|
||||||
this.paymentSource != null &&
|
this.paymentSource != null &&
|
||||||
|
|||||||
Reference in New Issue
Block a user