mirror of
https://github.com/bitwarden/browser
synced 2025-12-17 08:43:33 +00:00
[EC-8] Reuse user billing history for orgs
This commit is contained in:
@@ -1,89 +0,0 @@
|
|||||||
<div class="page-header d-flex">
|
|
||||||
<h1>
|
|
||||||
{{ "billingHistory" | i18n }}
|
|
||||||
</h1>
|
|
||||||
<button
|
|
||||||
(click)="load()"
|
|
||||||
class="btn btn-sm btn-outline-primary ml-auto"
|
|
||||||
*ngIf="firstLoaded"
|
|
||||||
[disabled]="loading"
|
|
||||||
>
|
|
||||||
<i class="bwi bwi-refresh bwi-fw" [ngClass]="{ 'bwi-spin': loading }" aria-hidden="true"></i>
|
|
||||||
{{ "refresh" | i18n }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<ng-container *ngIf="!firstLoaded && loading">
|
|
||||||
<i
|
|
||||||
class="bwi bwi-spinner bwi-spin text-muted"
|
|
||||||
title="{{ 'loading' | i18n }}"
|
|
||||||
aria-hidden="true"
|
|
||||||
></i>
|
|
||||||
<span class="sr-only">{{ "loading" | i18n }}</span>
|
|
||||||
</ng-container>
|
|
||||||
<ng-container *ngIf="billing">
|
|
||||||
<h2 class="mt-3">{{ "invoices" | i18n }}</h2>
|
|
||||||
<p *ngIf="!invoices || !invoices.length">{{ "noInvoices" | i18n }}</p>
|
|
||||||
<table class="table mb-2" *ngIf="invoices && invoices.length">
|
|
||||||
<tbody>
|
|
||||||
<tr *ngFor="let i of invoices">
|
|
||||||
<td>{{ i.date | date: "mediumDate" }}</td>
|
|
||||||
<td>
|
|
||||||
<a
|
|
||||||
href="{{ i.pdfUrl }}"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener"
|
|
||||||
class="mr-2"
|
|
||||||
appA11yTitle="{{ 'downloadInvoice' | i18n }}"
|
|
||||||
>
|
|
||||||
<i class="bwi bwi-file-pdf" aria-hidden="true"></i
|
|
||||||
></a>
|
|
||||||
<a href="{{ i.url }}" target="_blank" rel="noopener" title="{{ 'viewInvoice' | i18n }}">
|
|
||||||
{{ "invoiceNumber" | i18n: i.number }}</a
|
|
||||||
>
|
|
||||||
</td>
|
|
||||||
<td>{{ i.amount | currency: "$" }}</td>
|
|
||||||
<td>
|
|
||||||
<span *ngIf="i.paid">
|
|
||||||
<i class="bwi bwi-check text-success" aria-hidden="true"></i>
|
|
||||||
{{ "paid" | i18n }}
|
|
||||||
</span>
|
|
||||||
<span *ngIf="!i.paid">
|
|
||||||
<i class="bwi bwi-exclamation-circle text-muted" aria-hidden="true"></i>
|
|
||||||
{{ "unpaid" | i18n }}
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
<h2 class="spaced-header">{{ "transactions" | i18n }}</h2>
|
|
||||||
<p *ngIf="!transactions || !transactions.length">{{ "noTransactions" | i18n }}</p>
|
|
||||||
<table class="table mb-2" *ngIf="transactions && transactions.length">
|
|
||||||
<tbody>
|
|
||||||
<tr *ngFor="let t of transactions">
|
|
||||||
<td>{{ t.createdDate | date: "mediumDate" }}</td>
|
|
||||||
<td>
|
|
||||||
<span *ngIf="t.type === transactionType.Charge || t.type === transactionType.Credit">
|
|
||||||
{{ "chargeNoun" | i18n }}
|
|
||||||
</span>
|
|
||||||
<span *ngIf="t.type === transactionType.Refund">{{ "refundNoun" | i18n }}</span>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<i
|
|
||||||
class="bwi bwi-fw"
|
|
||||||
*ngIf="t.paymentMethodType"
|
|
||||||
aria-hidden="true"
|
|
||||||
[ngClass]="paymentMethodClasses(t.paymentMethodType)"
|
|
||||||
></i>
|
|
||||||
{{ t.details }}
|
|
||||||
</td>
|
|
||||||
<td
|
|
||||||
[ngClass]="{ 'text-strike': t.refunded }"
|
|
||||||
title="{{ (t.refunded ? 'refunded' : '') | i18n }}"
|
|
||||||
>
|
|
||||||
{{ t.amount | currency: "$" }}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
<small class="text-muted">* {{ "chargesStatement" | i18n: "BITWARDEN" }}</small>
|
|
||||||
</ng-container>
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
import { Component, OnInit } from "@angular/core";
|
|
||||||
import { ActivatedRoute } from "@angular/router";
|
|
||||||
|
|
||||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
|
||||||
import { PaymentMethodType } from "@bitwarden/common/enums/paymentMethodType";
|
|
||||||
import { TransactionType } from "@bitwarden/common/enums/transactionType";
|
|
||||||
import { BillingResponse } from "@bitwarden/common/models/response/billingResponse";
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: "app-org-billing-history",
|
|
||||||
templateUrl: "./organization-billing-history.component.html",
|
|
||||||
})
|
|
||||||
export class OrganizationBillingHistoryComponent implements OnInit {
|
|
||||||
loading = false;
|
|
||||||
firstLoaded = false;
|
|
||||||
billing: BillingResponse;
|
|
||||||
paymentMethodType = PaymentMethodType;
|
|
||||||
transactionType = TransactionType;
|
|
||||||
organizationId: string;
|
|
||||||
|
|
||||||
constructor(private apiService: ApiService, private route: ActivatedRoute) {}
|
|
||||||
|
|
||||||
async ngOnInit() {
|
|
||||||
this.route.parent.parent.params.subscribe(async (params) => {
|
|
||||||
this.organizationId = params.organizationId;
|
|
||||||
await this.load();
|
|
||||||
this.firstLoaded = true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async load() {
|
|
||||||
if (this.loading) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.loading = true;
|
|
||||||
if (this.organizationId != null) {
|
|
||||||
this.billing = await this.apiService.getOrganizationBilling(this.organizationId);
|
|
||||||
}
|
|
||||||
this.loading = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
get invoices() {
|
|
||||||
return this.billing != null ? this.billing.invoices : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
get transactions() {
|
|
||||||
return this.billing != null ? this.billing.transactions : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
paymentMethodClasses(type: PaymentMethodType) {
|
|
||||||
switch (type) {
|
|
||||||
case PaymentMethodType.Card:
|
|
||||||
return ["bwi-credit-card"];
|
|
||||||
case PaymentMethodType.BankAccount:
|
|
||||||
case PaymentMethodType.WireTransfer:
|
|
||||||
return ["bwi-bank"];
|
|
||||||
case PaymentMethodType.BitPay:
|
|
||||||
return ["bwi-bitcoin text-warning"];
|
|
||||||
case PaymentMethodType.PayPal:
|
|
||||||
return ["bwi-paypal text-primary"];
|
|
||||||
default:
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -4,7 +4,6 @@ import { LooseComponentsModule } from "../../loose-components.module";
|
|||||||
import { SharedModule } from "../../shared.module";
|
import { SharedModule } from "../../shared.module";
|
||||||
|
|
||||||
import { BillingSyncApiKeyComponent } from "./billing-sync-api-key.component";
|
import { BillingSyncApiKeyComponent } from "./billing-sync-api-key.component";
|
||||||
import { OrganizationBillingHistoryComponent } from "./organization-billing-history.component";
|
|
||||||
import { OrganizationBillingTabComponent } from "./organization-billing-tab.component";
|
import { OrganizationBillingTabComponent } from "./organization-billing-tab.component";
|
||||||
import { OrganizationPaymentMethodComponent } from "./organization-payment-method.component";
|
import { OrganizationPaymentMethodComponent } from "./organization-payment-method.component";
|
||||||
import { OrganizationSubscriptionComponent } from "./organization-subscription.component";
|
import { OrganizationSubscriptionComponent } from "./organization-subscription.component";
|
||||||
@@ -13,14 +12,12 @@ import { OrganizationSubscriptionComponent } from "./organization-subscription.c
|
|||||||
imports: [SharedModule, LooseComponentsModule],
|
imports: [SharedModule, LooseComponentsModule],
|
||||||
declarations: [
|
declarations: [
|
||||||
BillingSyncApiKeyComponent,
|
BillingSyncApiKeyComponent,
|
||||||
OrganizationBillingHistoryComponent,
|
|
||||||
OrganizationBillingTabComponent,
|
OrganizationBillingTabComponent,
|
||||||
OrganizationPaymentMethodComponent,
|
OrganizationPaymentMethodComponent,
|
||||||
OrganizationSubscriptionComponent,
|
OrganizationSubscriptionComponent,
|
||||||
],
|
],
|
||||||
exports: [
|
exports: [
|
||||||
BillingSyncApiKeyComponent,
|
BillingSyncApiKeyComponent,
|
||||||
OrganizationBillingHistoryComponent,
|
|
||||||
OrganizationBillingTabComponent,
|
OrganizationBillingTabComponent,
|
||||||
OrganizationPaymentMethodComponent,
|
OrganizationPaymentMethodComponent,
|
||||||
OrganizationSubscriptionComponent,
|
OrganizationSubscriptionComponent,
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import { RouterModule, Routes } from "@angular/router";
|
|||||||
import { AuthGuard } from "@bitwarden/angular/guards/auth.guard";
|
import { AuthGuard } from "@bitwarden/angular/guards/auth.guard";
|
||||||
import { Permissions } from "@bitwarden/common/enums/permissions";
|
import { Permissions } from "@bitwarden/common/enums/permissions";
|
||||||
|
|
||||||
import { OrganizationBillingHistoryComponent } from "../modules/organizations/billing/organization-billing-history.component";
|
|
||||||
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 { 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";
|
||||||
@@ -12,6 +11,7 @@ import { ReportListComponent } from "../modules/organizations/reporting/report-l
|
|||||||
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 { UserBillingHistoryComponent } from "./../settings/user-billing-history.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";
|
||||||
import { EventsComponent } from "./manage/events.component";
|
import { EventsComponent } from "./manage/events.component";
|
||||||
@@ -168,7 +168,7 @@ const routes: Routes = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "history",
|
path: "history",
|
||||||
component: OrganizationBillingHistoryComponent,
|
component: UserBillingHistoryComponent,
|
||||||
canActivate: [PermissionsGuard],
|
canActivate: [PermissionsGuard],
|
||||||
data: { titleId: "billingHistory", permissions: [Permissions.ManageBilling] },
|
data: { titleId: "billingHistory", permissions: [Permissions.ManageBilling] },
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<div class="tabbed-header d-flex">
|
<div class="d-flex" [ngClass]="headerClass">
|
||||||
<h1>
|
<h1>
|
||||||
{{ "billingHistory" | i18n }}
|
{{ "billingHistory" | i18n }}
|
||||||
</h1>
|
</h1>
|
||||||
@@ -23,7 +23,7 @@
|
|||||||
<span class="sr-only">{{ "loading" | i18n }}</span>
|
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<ng-container *ngIf="billing">
|
<ng-container *ngIf="billing">
|
||||||
<h2>{{ "invoices" | i18n }}</h2>
|
<h2 class="mt-3">{{ "invoices" | i18n }}</h2>
|
||||||
<p *ngIf="!invoices || !invoices.length">{{ "noInvoices" | i18n }}</p>
|
<p *ngIf="!invoices || !invoices.length">{{ "noInvoices" | i18n }}</p>
|
||||||
<table class="table mb-2" *ngIf="invoices && invoices.length">
|
<table class="table mb-2" *ngIf="invoices && invoices.length">
|
||||||
<tbody>
|
<tbody>
|
||||||
@@ -74,14 +74,7 @@
|
|||||||
class="bwi bwi-fw"
|
class="bwi bwi-fw"
|
||||||
*ngIf="t.paymentMethodType"
|
*ngIf="t.paymentMethodType"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
[ngClass]="{
|
[ngClass]="paymentMethodClasses(t.paymentMethodType)"
|
||||||
'bwi-credit-card': t.paymentMethodType === paymentMethodType.Card,
|
|
||||||
'bwi-bank':
|
|
||||||
t.paymentMethodType === paymentMethodType.BankAccount ||
|
|
||||||
t.paymentMethodType === paymentMethodType.WireTransfer,
|
|
||||||
'bwi-bitcoin text-warning': t.paymentMethodType === paymentMethodType.BitPay,
|
|
||||||
'bwi-paypal text-primary': t.paymentMethodType === paymentMethodType.PayPal
|
|
||||||
}"
|
|
||||||
></i>
|
></i>
|
||||||
{{ t.details }}
|
{{ t.details }}
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Component, OnInit } from "@angular/core";
|
import { Component, OnInit } 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";
|
||||||
@@ -18,20 +18,27 @@ export class UserBillingHistoryComponent implements OnInit {
|
|||||||
billing: BillingHistoryResponse;
|
billing: BillingHistoryResponse;
|
||||||
paymentMethodType = PaymentMethodType;
|
paymentMethodType = PaymentMethodType;
|
||||||
transactionType = TransactionType;
|
transactionType = TransactionType;
|
||||||
|
organizationId?: string;
|
||||||
|
|
||||||
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 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 +46,13 @@ export class UserBillingHistoryComponent implements OnInit {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
this.billing = await this.apiService.getUserBillingHistory();
|
|
||||||
|
if (this.forOrganization) {
|
||||||
|
this.billing = await this.apiService.getOrganizationBilling(this.organizationId);
|
||||||
|
} else {
|
||||||
|
this.billing = await this.apiService.getUserBillingHistory();
|
||||||
|
}
|
||||||
|
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,4 +63,28 @@ export class UserBillingHistoryComponent implements OnInit {
|
|||||||
get transactions() {
|
get transactions() {
|
||||||
return this.billing != null ? this.billing.transactions : null;
|
return this.billing != null ? this.billing.transactions : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get forOrganization() {
|
||||||
|
return this.organizationId != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
get headerClass() {
|
||||||
|
return this.forOrganization ? ["page-header"] : ["tabbed-header"];
|
||||||
|
}
|
||||||
|
|
||||||
|
paymentMethodClasses(type: PaymentMethodType) {
|
||||||
|
switch (type) {
|
||||||
|
case PaymentMethodType.Card:
|
||||||
|
return ["bwi-credit-card"];
|
||||||
|
case PaymentMethodType.BankAccount:
|
||||||
|
case PaymentMethodType.WireTransfer:
|
||||||
|
return ["bwi-bank"];
|
||||||
|
case PaymentMethodType.BitPay:
|
||||||
|
return ["bwi-bitcoin text-warning"];
|
||||||
|
case PaymentMethodType.PayPal:
|
||||||
|
return ["bwi-paypal text-primary"];
|
||||||
|
default:
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,10 @@ export class BillingResponse extends BaseResponse {
|
|||||||
this.invoices = invoices.map((i: any) => new BillingInvoiceResponse(i));
|
this.invoices = invoices.map((i: any) => new BillingInvoiceResponse(i));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get hasNoHistory() {
|
||||||
|
return this.invoices.length == 0 && this.transactions.length == 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class BillingSourceResponse extends BaseResponse {
|
export class BillingSourceResponse extends BaseResponse {
|
||||||
|
|||||||
Reference in New Issue
Block a user