From 9ca547acb1286443555a9eaf0bf8754f261fd727 Mon Sep 17 00:00:00 2001 From: Yehor Butko Date: Wed, 20 Nov 2019 15:46:22 +0200 Subject: [PATCH] web/satellite: project charges (#3611) --- .../console/consoleweb/consoleapi/payments.go | 15 +++- satellite/payments/projectchardges.go | 6 +- .../payments/stripecoinpayments/accounts.go | 6 +- web/satellite/src/api/payments.ts | 17 ++++- .../monthlySummary/MonthlyBillingSummary.vue | 73 ++++++++++++++----- .../monthlySummary/UsageChargeItem.vue | 39 ++++++++-- web/satellite/src/main.ts | 7 ++ web/satellite/src/store/modules/payments.ts | 15 +++- web/satellite/src/types/payments.ts | 25 ++++++- web/satellite/src/views/DashboardArea.vue | 3 +- 10 files changed, 164 insertions(+), 42 deletions(-) diff --git a/satellite/console/consoleweb/consoleapi/payments.go b/satellite/console/consoleweb/consoleapi/payments.go index 9c30dc1fa..30bc9fec4 100644 --- a/satellite/console/consoleweb/consoleapi/payments.go +++ b/satellite/console/consoleweb/consoleapi/payments.go @@ -23,7 +23,7 @@ var ( mon = monkit.Package() ) -// Payments is an api controller that exposes all payment related functionality +// Payments is an api controller that exposes all payment related functionality. type Payments struct { log *zap.Logger service *console.Service @@ -61,6 +61,8 @@ func (p *Payments) AccountBalance(w http.ResponseWriter, r *http.Request) { var err error defer mon.Task()(&ctx)(&err) + w.Header().Set("Content-Type", "application/json") + balance, err := p.service.Payments().AccountBalance(ctx) if err != nil { if console.ErrUnauthorized.Has(err) { @@ -72,7 +74,6 @@ func (p *Payments) AccountBalance(w http.ResponseWriter, r *http.Request) { return } - w.Header().Set("Content-Type", "application/json") err = json.NewEncoder(w).Encode(&balance) if err != nil { p.log.Error("failed to write json balance response", zap.Error(ErrPaymentsAPI.Wrap(err))) @@ -85,6 +86,8 @@ func (p *Payments) ProjectsCharges(w http.ResponseWriter, r *http.Request) { var err error defer mon.Task()(&ctx)(&err) + w.Header().Set("Content-Type", "application/json") + charges, err := p.service.Payments().ProjectsCharges(ctx) if err != nil { if console.ErrUnauthorized.Has(err) { @@ -134,6 +137,8 @@ func (p *Payments) ListCreditCards(w http.ResponseWriter, r *http.Request) { var err error defer mon.Task()(&ctx)(&err) + w.Header().Set("Content-Type", "application/json") + cards, err := p.service.Payments().ListCreditCards(ctx) if err != nil { if console.ErrUnauthorized.Has(err) { @@ -145,7 +150,6 @@ func (p *Payments) ListCreditCards(w http.ResponseWriter, r *http.Request) { return } - w.Header().Set("Content-Type", "application/json") err = json.NewEncoder(w).Encode(cards) if err != nil { p.log.Error("failed to write json list cards response", zap.Error(ErrPaymentsAPI.Wrap(err))) @@ -208,6 +212,8 @@ func (p *Payments) BillingHistory(w http.ResponseWriter, r *http.Request) { var err error defer mon.Task()(&ctx)(&err) + w.Header().Set("Content-Type", "application/json") + billingHistory, err := p.service.Payments().BillingHistory(ctx) if err != nil { if console.ErrUnauthorized.Has(err) { @@ -219,7 +225,6 @@ func (p *Payments) BillingHistory(w http.ResponseWriter, r *http.Request) { return } - w.Header().Set("Content-Type", "application/json") err = json.NewEncoder(w).Encode(billingHistory) if err != nil { p.log.Error("failed to write json billing history response", zap.Error(ErrPaymentsAPI.Wrap(err))) @@ -233,6 +238,8 @@ func (p *Payments) TokenDeposit(w http.ResponseWriter, r *http.Request) { defer mon.Task()(&ctx)(&err) + w.Header().Set("Content-Type", "application/json") + var requestData struct { Amount string `json:"amount"` } diff --git a/satellite/payments/projectchardges.go b/satellite/payments/projectchardges.go index 2f52796c2..89ae6d4a9 100644 --- a/satellite/payments/projectchardges.go +++ b/satellite/payments/projectchardges.go @@ -11,9 +11,9 @@ import ( type ProjectCharge struct { ProjectID uuid.UUID `json:"projectId"` // StorageGbHrs shows how much cents we should pay for storing GB*Hrs. - StorageGbHrs int64 + StorageGbHrs int64 `json:"storage"` // Egress shows how many cents we should pay for Egress. - Egress int64 + Egress int64 `json:"egress"` // ObjectCount shows how many cents we should pay for objects count. - ObjectCount int64 + ObjectCount int64 `json:"objectCount"` } diff --git a/satellite/payments/stripecoinpayments/accounts.go b/satellite/payments/stripecoinpayments/accounts.go index d4026a75f..c9dc53693 100644 --- a/satellite/payments/stripecoinpayments/accounts.go +++ b/satellite/payments/stripecoinpayments/accounts.go @@ -95,10 +95,10 @@ func (accounts *accounts) ProjectCharges(ctx context.Context, userID uuid.UUID) charges = append(charges, payments.ProjectCharge{ ProjectID: project.ID, - Egress: usage.Egress / int64(memory.TB) * accounts.service.EgressPrice, // TODO: check precision - ObjectCount: int64(usage.ObjectCount) * accounts.service.PerObjectPrice, - StorageGbHrs: int64(usage.Storage) / int64(memory.TB) * accounts.service.TBhPrice, + Egress: usage.Egress * accounts.service.EgressPrice / int64(memory.TB), + ObjectCount: int64(usage.ObjectCount * float64(accounts.service.PerObjectPrice)), + StorageGbHrs: int64(usage.Storage*float64(accounts.service.TBhPrice)) / int64(memory.TB), }) } diff --git a/web/satellite/src/api/payments.ts b/web/satellite/src/api/payments.ts index 92cca0c3a..f1b23c12a 100644 --- a/web/satellite/src/api/payments.ts +++ b/web/satellite/src/api/payments.ts @@ -2,7 +2,7 @@ // See LICENSE for copying information. import { ErrorUnauthorized } from '@/api/errors/ErrorUnauthorized'; -import { BillingHistoryItem, CreditCard, PaymentsApi } from '@/types/payments'; +import { BillingHistoryItem, CreditCard, PaymentsApi, ProjectCharge } from '@/types/payments'; import { HttpClient } from '@/utils/httpClient'; /** @@ -54,7 +54,10 @@ export class PaymentsHttpApi implements PaymentsApi { throw new Error('can not setup account'); } - public async projectsCharges(): Promise { + /** + * projectsCharges returns how much money current user will be charged for each project which he owns. + */ + public async projectsCharges(): Promise { const path = `${this.ROOT_PATH}/account/charges`; const response = await this.client.get(path); @@ -66,8 +69,16 @@ export class PaymentsHttpApi implements PaymentsApi { throw new Error('can not get projects charges'); } - // TODO: fiish mapping const charges = await response.json(); + if (charges) { + return charges.map(charge => + new ProjectCharge( + charge.projectId, + charge.storage, + charge.egress, + charge.objectCount), + ); + } return []; } diff --git a/web/satellite/src/components/account/billing/monthlySummary/MonthlyBillingSummary.vue b/web/satellite/src/components/account/billing/monthlySummary/MonthlyBillingSummary.vue index 7b43db070..c5ae01903 100644 --- a/web/satellite/src/components/account/billing/monthlySummary/MonthlyBillingSummary.vue +++ b/web/satellite/src/components/account/billing/monthlySummary/MonthlyBillingSummary.vue @@ -6,15 +6,7 @@

Current Month

-

{{currentPeriod}}

-
-
- Usage $12.44 - +

{{ currentPeriod }}

@@ -22,18 +14,25 @@
- - - - - - - Usage Charges +
+ + + + + + +
+ Usage Charges
- Estimated total $82.44 + Estimated total {{ chargesSummary | centsToDollars }}
- +
@@ -46,6 +45,8 @@ import { Component, Vue } from 'vue-property-decorator'; import UsageChargeItem from '@/components/account/billing/monthlySummary/UsageChargeItem.vue'; import VButton from '@/components/common/VButton.vue'; +import { ProjectCharge } from '@/types/payments'; + @Component({ components: { VButton, @@ -53,8 +54,17 @@ import VButton from '@/components/common/VButton.vue'; }, }) export default class MonthlyBillingSummary extends Vue { + /** + * areUsageChargesShown indicates if area with all projects is expanded. + */ private areUsageChargesShown: boolean = false; + /** + * usageCharges is an array of all ProjectCharges. + */ + public usageCharges: ProjectCharge[] = this.$store.state.paymentsModule.charges; + + // TODO: unused. public get currentPeriod(): string { const months: string[] = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']; const now: Date = new Date(); @@ -69,6 +79,18 @@ export default class MonthlyBillingSummary extends Vue { return `${months[monthNumber]} 1 - ${date} ${year}`; } + /** + * chargesSummary returns summary of all projects. + */ + public get chargesSummary(): number { + const usageItemSummaries = this.usageCharges.map(item => item.summary()); + + return usageItemSummaries.reduce((accumulator, current) => accumulator + current); + } + + /** + * toggleUsageChargesPopup is used to open/close area with list of project charges. + */ public toggleUsageChargesPopup(): void { this.areUsageChargesShown = !this.areUsageChargesShown; } @@ -102,6 +124,7 @@ export default class MonthlyBillingSummary extends Vue { font-family: 'font_bold', sans-serif; font-size: 32px; line-height: 48px; + user-select: none; } &__title-info { @@ -132,6 +155,7 @@ export default class MonthlyBillingSummary extends Vue { font-size: 14px; line-height: 21px; color: #afb7c1; + user-select: none; } &__usage-charges { @@ -151,9 +175,20 @@ export default class MonthlyBillingSummary extends Vue { display: flex; align-items: center; - &__image { + &__image-container { + max-width: 14px; + max-height: 14px; + width: 14px; + height: 14px; + display: flex; + align-items: center; + justify-content: center; margin-right: 12px; } + + &__title { + user-select: none; + } } } diff --git a/web/satellite/src/components/account/billing/monthlySummary/UsageChargeItem.vue b/web/satellite/src/components/account/billing/monthlySummary/UsageChargeItem.vue index 14ce09d6a..7c519b87c 100644 --- a/web/satellite/src/components/account/billing/monthlySummary/UsageChargeItem.vue +++ b/web/satellite/src/components/account/billing/monthlySummary/UsageChargeItem.vue @@ -11,34 +11,61 @@ - Project 2 + {{ projectName }} - $12.88 + {{ item.summary() | centsToDollars }}
Storage - $18.00 + {{ item.storage | centsToDollars }}
Egress - $2.00 + {{ item.egress | centsToDollars }}
Objects - $1.22 + {{ item.objectCount | centsToDollars }}