Skip to content

Commit

Permalink
web/satellite: show overridden usage prices in the satellite UI
Browse files Browse the repository at this point in the history
This change allows users who register with a partner that has
different project usage prices to see the correct prices in the
satellite UI.

Resolves storj/storj-private#90

Change-Id: I06bde50db474b25396671a27e282ef5637efe85b
  • Loading branch information
jewharton authored and Storj Robot committed Jan 17, 2023
1 parent 5d656e6 commit 6142b1c
Show file tree
Hide file tree
Showing 17 changed files with 210 additions and 40 deletions.
1 change: 0 additions & 1 deletion satellite/api.go
Expand Up @@ -646,7 +646,6 @@ func NewAPI(log *zap.Logger, full *identity.FullIdentity, db DB,
accountFreezeService,
peer.Console.Listener,
config.Payments.StripeCoinPayments.StripePublicKey,
config.Payments.UsagePrice,
peer.URL(),
)

Expand Down
24 changes: 24 additions & 0 deletions satellite/console/consoleweb/consoleapi/payments.go
Expand Up @@ -432,6 +432,30 @@ func (p *Payments) WalletPayments(w http.ResponseWriter, r *http.Request) {
}
}

// GetProjectUsagePriceModel returns the project usage price model for the user.
func (p *Payments) GetProjectUsagePriceModel(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var err error
defer mon.Task()(&ctx)(&err)

w.Header().Set("Content-Type", "application/json")

pricing, err := p.service.Payments().GetProjectUsagePriceModel(ctx)
if err != nil {
if console.ErrUnauthorized.Has(err) {
p.serveJSONError(w, http.StatusUnauthorized, err)
return
}

p.serveJSONError(w, http.StatusInternalServerError, err)
return
}

if err = json.NewEncoder(w).Encode(pricing); err != nil {
p.log.Error("failed to encode project usage price model", zap.Error(ErrPaymentsAPI.Wrap(err)))
}
}

// serveJSONError writes JSON error to response output stream.
func (p *Payments) serveJSONError(w http.ResponseWriter, status int, err error) {
web.ServeJSONError(p.log, w, status, err)
Expand Down
13 changes: 2 additions & 11 deletions satellite/console/consoleweb/server.go
Expand Up @@ -42,7 +42,6 @@ import (
"storj.io/storj/satellite/console/consoleweb/consolewebauth"
"storj.io/storj/satellite/mailservice"
"storj.io/storj/satellite/oidc"
"storj.io/storj/satellite/payments/paymentsconfig"
"storj.io/storj/satellite/rewards"
)

Expand Down Expand Up @@ -134,8 +133,6 @@ type Server struct {

stripePublicKey string

usagePrice paymentsconfig.ProjectUsagePrice

schema graphql.Schema

templatesCache *templates
Expand Down Expand Up @@ -205,7 +202,7 @@ func (a *apiAuth) RemoveAuthCookie(w http.ResponseWriter) {
}

// NewServer creates new instance of console server.
func NewServer(logger *zap.Logger, config Config, service *console.Service, oidcService *oidc.Service, mailService *mailservice.Service, partners *rewards.PartnersService, analytics *analytics.Service, abTesting *abtesting.Service, accountFreezeService *console.AccountFreezeService, listener net.Listener, stripePublicKey string, usagePrice paymentsconfig.ProjectUsagePrice, nodeURL storj.NodeURL) *Server {
func NewServer(logger *zap.Logger, config Config, service *console.Service, oidcService *oidc.Service, mailService *mailservice.Service, partners *rewards.PartnersService, analytics *analytics.Service, abTesting *abtesting.Service, accountFreezeService *console.AccountFreezeService, listener net.Listener, stripePublicKey string, nodeURL storj.NodeURL) *Server {
server := Server{
log: logger,
config: config,
Expand All @@ -219,7 +216,6 @@ func NewServer(logger *zap.Logger, config Config, service *console.Service, oidc
ipRateLimiter: web.NewIPRateLimiter(config.RateLimit, logger),
userIDRateLimiter: NewUserIDRateLimiter(config.RateLimit, logger),
nodeURL: nodeURL,
usagePrice: usagePrice,
}

logger.Debug("Starting Satellite UI.", zap.Stringer("Address", server.listener.Addr()))
Expand Down Expand Up @@ -320,6 +316,7 @@ func NewServer(logger *zap.Logger, config Config, service *console.Service, oidc
paymentsRouter.HandleFunc("/billing-history", paymentController.BillingHistory).Methods(http.MethodGet)
paymentsRouter.Handle("/coupon/apply", server.userIDRateLimiter.Limit(http.HandlerFunc(paymentController.ApplyCouponCode))).Methods(http.MethodPatch)
paymentsRouter.HandleFunc("/coupon", paymentController.GetCoupon).Methods(http.MethodGet)
paymentsRouter.HandleFunc("/pricing", paymentController.GetProjectUsagePriceModel).Methods(http.MethodGet)

bucketsController := consoleapi.NewBuckets(logger, service)
bucketsRouter := router.PathPrefix("/api/v0/buckets").Subrouter()
Expand Down Expand Up @@ -452,9 +449,6 @@ func (server *Server) appHandler(w http.ResponseWriter, r *http.Request) {
FileBrowserFlowDisabled bool
LinksharingURL string
PathwayOverviewEnabled bool
StorageTBPrice string
EgressTBPrice string
SegmentPrice string
RegistrationRecaptchaEnabled bool
RegistrationRecaptchaSiteKey string
RegistrationHcaptchaEnabled bool
Expand Down Expand Up @@ -499,9 +493,6 @@ func (server *Server) appHandler(w http.ResponseWriter, r *http.Request) {
data.PathwayOverviewEnabled = server.config.PathwayOverviewEnabled
data.DefaultPaidStorageLimit = server.config.UsageLimits.Storage.Paid
data.DefaultPaidBandwidthLimit = server.config.UsageLimits.Bandwidth.Paid
data.StorageTBPrice = server.usagePrice.StorageTB
data.EgressTBPrice = server.usagePrice.EgressTB
data.SegmentPrice = server.usagePrice.Segment
data.RegistrationRecaptchaEnabled = server.config.Captcha.Registration.Recaptcha.Enabled
data.RegistrationRecaptchaSiteKey = server.config.Captcha.Registration.Recaptcha.SiteKey
data.RegistrationHcaptchaEnabled = server.config.Captcha.Registration.Hcaptcha.Enabled
Expand Down
13 changes: 13 additions & 0 deletions satellite/console/service.go
Expand Up @@ -3043,6 +3043,19 @@ func (payment Payments) WalletPayments(ctx context.Context) (_ WalletPayments, e
}, nil
}

// GetProjectUsagePriceModel returns the project usage price model for the user.
func (payment Payments) GetProjectUsagePriceModel(ctx context.Context) (_ *payments.ProjectUsagePriceModel, err error) {
defer mon.Task()(&ctx)(&err)

user, err := GetUser(ctx)
if err != nil {
return nil, Error.Wrap(err)
}

model := payment.service.accounts.GetProjectUsagePriceModel(user.UserAgent)
return &model, nil
}

func findMembershipByProjectID(memberships []ProjectMember, projectID uuid.UUID) (ProjectMember, bool) {
for _, membership := range memberships {
if membership.ProjectID == projectID {
Expand Down
3 changes: 0 additions & 3 deletions web/satellite/index.html
Expand Up @@ -20,9 +20,6 @@
<meta name="coupon-code-signup-ui-enabled" content="{{ .CouponCodeSignupUIEnabled }}">
<meta name="file-browser-flow-disabled" content="{{ .FileBrowserFlowDisabled }}">
<meta name="linksharing-url" content="{{ .LinksharingURL }}">
<meta name="storage-tb-price" content="{{ .StorageTBPrice }}">
<meta name="egress-tb-price" content="{{ .EgressTBPrice }}">
<meta name="segment-price" content="{{ .SegmentPrice }}">
<meta name="registration-recaptcha-enabled" content="{{ .RegistrationRecaptchaEnabled }}">
<meta name="registration-recaptcha-site-key" content="{{ .RegistrationRecaptchaSiteKey }}">
<meta name="registration-hcaptcha-enabled" content="{{ .RegistrationHcaptchaEnabled }}">
Expand Down
20 changes: 20 additions & 0 deletions web/satellite/src/api/payments.ts
Expand Up @@ -10,6 +10,7 @@ import {
PaymentsApi,
PaymentsHistoryItem,
ProjectUsageAndCharges,
ProjectUsagePriceModel,
TokenAmount,
NativePaymentHistoryItem,
Wallet,
Expand Down Expand Up @@ -97,6 +98,25 @@ export class PaymentsHttpApi implements PaymentsApi {
return [];
}

/**
* projectUsagePriceModel returns usage and how much money current user will be charged for each project which he owns.
*/
public async projectUsagePriceModel(): Promise<ProjectUsagePriceModel> {
const path = `${this.ROOT_PATH}/pricing`;
const response = await this.client.get(path);

if (!response.ok) {
throw new Error('cannot get project usage price model');
}

const model = await response.json();
if (model) {
return new ProjectUsagePriceModel(model.storageMBMonthCents, model.egressMBCents, model.segmentMonthCents);
}

return new ProjectUsagePriceModel();
}

/**
* Add credit card.
*
Expand Down
Expand Up @@ -133,6 +133,7 @@ export default class BillingArea extends Vue {
try {
await this.$store.dispatch(PAYMENTS_ACTIONS.GET_PROJECT_USAGE_AND_CHARGES_CURRENT_ROLLUP);
await this.$store.dispatch(PAYMENTS_ACTIONS.GET_PROJECT_USAGE_PRICE_MODEL);
this.isDataFetching = false;
} catch (error) {
Expand Down
Expand Up @@ -61,6 +61,7 @@ export default class EstimatedCostsAndCredits extends Vue {
try {
await this.$store.dispatch(PAYMENTS_ACTIONS.GET_PROJECT_USAGE_AND_CHARGES_CURRENT_ROLLUP);
await this.$store.dispatch(PAYMENTS_ACTIONS.GET_PROJECT_USAGE_PRICE_MODEL);
this.isDataFetching = false;
} catch (error) {
Expand Down
Expand Up @@ -49,11 +49,11 @@
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
import { ProjectUsageAndCharges } from '@/types/payments';
import { ProjectUsageAndCharges, ProjectUsagePriceModel } from '@/types/payments';
import { Project } from '@/types/projects';
import { Size } from '@/utils/bytesSize';
import { SHORT_MONTHS_NAMES } from '@/utils/constants/date';
import { MetaUtils } from '@/utils/meta';
import { decimalShift } from '@/utils/strings';
import { AnalyticsHttpApi } from '@/api/analytics';
import { AnalyticsEvent } from '@/utils/constants/analyticsEventNames';
Expand Down Expand Up @@ -83,9 +83,10 @@ export default class UsageAndChargesItem extends Vue {
private readonly HOURS_IN_MONTH: number = 720;
/**
* GB_IN_TB constant shows amount of GBs in one TB.
* CENTS_MB_TO_DOLLARS_GB_SHIFT constant represents how many places to the left
* a decimal point must be shifted to convert from cents/MB to dollars/GB.
*/
private readonly GB_IN_TB = 1000;
private readonly CENTS_MB_TO_DOLLARS_GB_SHIFT = -1;
/**
* projectName returns project name.
Expand Down Expand Up @@ -133,22 +134,29 @@ export default class UsageAndChargesItem extends Vue {
/**
* Returns storage price per GB.
*/
public get storagePrice(): number {
return parseInt(MetaUtils.getMetaContent('storage-tb-price')) / this.GB_IN_TB;
public get storagePrice(): string {
return decimalShift(this.priceModel.storageMBMonthCents, this.CENTS_MB_TO_DOLLARS_GB_SHIFT);
}
/**
* Returns egress price per GB.
*/
public get egressPrice(): number {
return parseInt(MetaUtils.getMetaContent('egress-tb-price')) / this.GB_IN_TB;
public get egressPrice(): string {
return decimalShift(this.priceModel.egressMBCents, this.CENTS_MB_TO_DOLLARS_GB_SHIFT);
}
/**
* Returns segment price.
*/
public get segmentPrice(): string {
return MetaUtils.getMetaContent('segment-price');
return decimalShift(this.priceModel.segmentMonthCents, 2);
}
/**
* Returns project usage price model from store.
*/
private get priceModel(): ProjectUsagePriceModel {
return this.$store.state.paymentsModule.usagePriceModel;
}
/**
Expand Down
Expand Up @@ -54,11 +54,11 @@
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
import { ProjectUsageAndCharges } from '@/types/payments';
import { ProjectUsageAndCharges, ProjectUsagePriceModel } from '@/types/payments';
import { Project } from '@/types/projects';
import { Size } from '@/utils/bytesSize';
import { SHORT_MONTHS_NAMES } from '@/utils/constants/date';
import { MetaUtils } from '@/utils/meta';
import { decimalShift } from '@/utils/strings';
import GreyChevron from '@/../static/images/common/greyChevron.svg';
Expand All @@ -81,9 +81,10 @@ export default class UsageAndChargesItem2 extends Vue {
private readonly HOURS_IN_MONTH: number = 720;
/**
* GB_IN_TB constant shows amount of GBs in one TB.
* CENTS_MB_TO_DOLLARS_GB_SHIFT constant represents how many places to the left
* a decimal point must be shifted to convert from cents/MB to dollars/GB.
*/
private readonly GB_IN_TB = 1000;
private readonly CENTS_MB_TO_DOLLARS_GB_SHIFT = -1;
public paymentMethod = 'test';
Expand Down Expand Up @@ -133,22 +134,29 @@ export default class UsageAndChargesItem2 extends Vue {
/**
* Returns storage price per GB.
*/
public get storagePrice(): number {
return parseInt(MetaUtils.getMetaContent('storage-tb-price')) / this.GB_IN_TB;
public get storagePrice(): string {
return decimalShift(this.priceModel.storageMBMonthCents, this.CENTS_MB_TO_DOLLARS_GB_SHIFT);
}
/**
* Returns egress price per GB.
*/
public get egressPrice(): number {
return parseInt(MetaUtils.getMetaContent('egress-tb-price')) / this.GB_IN_TB;
public get egressPrice(): string {
return decimalShift(this.priceModel.egressMBCents, this.CENTS_MB_TO_DOLLARS_GB_SHIFT);
}
/**
* Returns segment price.
*/
public get segmentPrice(): string {
return MetaUtils.getMetaContent('segment-price');
return decimalShift(this.priceModel.segmentMonthCents, 2);
}
/**
* Returns project usage price model from store.
*/
private get priceModel(): ProjectUsagePriceModel {
return this.$store.state.paymentsModule.usagePriceModel;
}
/**
Expand Down
14 changes: 14 additions & 0 deletions web/satellite/src/store/modules/payments.ts
Expand Up @@ -11,6 +11,7 @@ import {
PaymentsHistoryItemStatus,
PaymentsHistoryItemType,
ProjectUsageAndCharges,
ProjectUsagePriceModel,
NativePaymentHistoryItem,
Wallet,
} from '@/types/payments';
Expand All @@ -27,6 +28,7 @@ export const PAYMENTS_MUTATIONS = {
SET_PAYMENTS_HISTORY: 'SET_PAYMENTS_HISTORY',
SET_NATIVE_PAYMENTS_HISTORY: 'SET_NATIVE_PAYMENTS_HISTORY',
SET_PROJECT_USAGE_AND_CHARGES: 'SET_PROJECT_USAGE_AND_CHARGES',
SET_PROJECT_USAGE_PRICE_MODEL: 'SET_PROJECT_USAGE_PRICE_MODEL',
SET_CURRENT_ROLLUP_PRICE: 'SET_CURRENT_ROLLUP_PRICE',
SET_PREVIOUS_ROLLUP_PRICE: 'SET_PREVIOUS_ROLLUP_PRICE',
SET_PRICE_SUMMARY: 'SET_PRICE_SUMMARY',
Expand All @@ -51,6 +53,7 @@ export const PAYMENTS_ACTIONS = {
GET_PROJECT_USAGE_AND_CHARGES: 'getProjectUsageAndCharges',
GET_PROJECT_USAGE_AND_CHARGES_CURRENT_ROLLUP: 'getProjectUsageAndChargesCurrentRollup',
GET_PROJECT_USAGE_AND_CHARGES_PREVIOUS_ROLLUP: 'getProjectUsageAndChargesPreviousRollup',
GET_PROJECT_USAGE_PRICE_MODEL: 'getProjectUsagePriceModel',
APPLY_COUPON_CODE: 'applyCouponCode',
GET_COUPON: `getCoupon`,
};
Expand All @@ -66,6 +69,7 @@ const {
SET_PAYMENTS_HISTORY,
SET_NATIVE_PAYMENTS_HISTORY,
SET_PROJECT_USAGE_AND_CHARGES,
SET_PROJECT_USAGE_PRICE_MODEL: SET_PROJECT_USAGE_PRICE_MODEL,
SET_PRICE_SUMMARY,
SET_PRICE_SUMMARY_FOR_SELECTED_PROJECT,
SET_COUPON,
Expand All @@ -87,6 +91,7 @@ const {
GET_NATIVE_PAYMENTS_HISTORY,
GET_PROJECT_USAGE_AND_CHARGES_CURRENT_ROLLUP,
GET_PROJECT_USAGE_AND_CHARGES_PREVIOUS_ROLLUP,
GET_PROJECT_USAGE_PRICE_MODEL: GET_PROJECT_USAGE_PRICE_MODEL,
APPLY_COUPON_CODE,
GET_COUPON,
} = PAYMENTS_ACTIONS;
Expand All @@ -100,6 +105,7 @@ export class PaymentsState {
public paymentsHistory: PaymentsHistoryItem[] = [];
public nativePaymentsHistory: NativePaymentHistoryItem[] = [];
public usageAndCharges: ProjectUsageAndCharges[] = [];
public usagePriceModel: ProjectUsagePriceModel = new ProjectUsagePriceModel();
public priceSummary = 0;
public priceSummaryForSelectedProject = 0;
public startDate: Date = new Date();
Expand Down Expand Up @@ -175,6 +181,9 @@ export function makePaymentsModule(api: PaymentsApi): StoreModule<PaymentsState,
[SET_PROJECT_USAGE_AND_CHARGES](state: PaymentsState, usageAndCharges: ProjectUsageAndCharges[]): void {
state.usageAndCharges = usageAndCharges;
},
[SET_PROJECT_USAGE_PRICE_MODEL](state: PaymentsState, model: ProjectUsagePriceModel): void {
state.usagePriceModel = model;
},
[SET_PRICE_SUMMARY](state: PaymentsState, charges: ProjectUsageAndCharges[]): void {
if (charges.length === 0) {
state.priceSummary = 0;
Expand Down Expand Up @@ -209,6 +218,7 @@ export function makePaymentsModule(api: PaymentsApi): StoreModule<PaymentsState,
state.paymentsHistory = [];
state.nativePaymentsHistory = [];
state.usageAndCharges = [];
state.usagePriceModel = new ProjectUsagePriceModel();
state.priceSummary = 0;
state.priceSummaryForSelectedProject = 0;
state.startDate = new Date();
Expand Down Expand Up @@ -302,6 +312,10 @@ export function makePaymentsModule(api: PaymentsApi): StoreModule<PaymentsState,
commit(SET_PROJECT_USAGE_AND_CHARGES, usageAndCharges);
commit(SET_PRICE_SUMMARY, usageAndCharges);
},
[GET_PROJECT_USAGE_PRICE_MODEL]: async function({ commit }: PaymentsContext): Promise<void> {
const model: ProjectUsagePriceModel = await api.projectUsagePriceModel();
commit(SET_PROJECT_USAGE_PRICE_MODEL, model);
},
[APPLY_COUPON_CODE]: async function({ commit }: PaymentsContext, code: string): Promise<void> {
const coupon = await api.applyCouponCode(code);
commit(SET_COUPON, coupon);
Expand Down

0 comments on commit 6142b1c

Please sign in to comment.