diff --git a/apps/web/src/app/billing/individual/user-subscription.component.html b/apps/web/src/app/billing/individual/user-subscription.component.html
index dca77dbf950..4c600b421c2 100644
--- a/apps/web/src/app/billing/individual/user-subscription.component.html
+++ b/apps/web/src/app/billing/individual/user-subscription.component.html
@@ -90,17 +90,6 @@
{{ i.quantity * i.amount | currency : "$" }} /{{ i.interval | i18n }} |
-
- |
-
- {{ "customBillingStart" | i18n }}
-
- {{ "billingHistory" | i18n }}
-
- {{ "customBillingEnd" | i18n }}
-
- |
-
diff --git a/apps/web/src/app/billing/individual/user-subscription.component.ts b/apps/web/src/app/billing/individual/user-subscription.component.ts
index 5b4b7cf49ef..abb5fd06428 100644
--- a/apps/web/src/app/billing/individual/user-subscription.component.ts
+++ b/apps/web/src/app/billing/individual/user-subscription.component.ts
@@ -205,10 +205,6 @@ export class UserSubscriptionComponent implements OnInit {
return this.sub != null ? this.sub.upcomingInvoice : null;
}
- get discount() {
- return this.sub != null ? this.sub.discount : null;
- }
-
get storagePercentage() {
return this.sub != null && this.sub.maxStorageGb
? +(100 * (this.sub.storageGb / this.sub.maxStorageGb)).toFixed(2)
diff --git a/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.html b/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.html
index bfb94a389ed..62d17a7e00d 100644
--- a/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.html
+++ b/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.html
@@ -69,7 +69,7 @@
-
+
|
{{ i.productName }} -
{{ i.name }} {{ i.quantity > 1 ? "×" + i.quantity : "" }} @
@@ -79,17 +79,6 @@
{{ i.quantity * i.amount | currency : "$" }} /{{ i.interval | i18n }}
|
-
- |
-
- {{ "customBillingStart" | i18n }}
-
- {{ "billingHistory" | i18n }}
-
- {{ "customBillingEnd" | i18n }}
-
- |
-
@@ -150,6 +139,7 @@
diff --git a/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.ts b/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.ts
index eb7180f7c83..d9e81e5f6b0 100644
--- a/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.ts
+++ b/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.ts
@@ -134,12 +134,24 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy
return this.sub != null ? this.sub.subscription : null;
}
+ get subscriptionLineItems() {
+ return this.lineItems.map((lineItem: BillingSubscriptionItemResponse) => ({
+ name: lineItem.name,
+ amount: this.discountPrice(lineItem.amount),
+ quantity: lineItem.quantity,
+ interval: lineItem.interval,
+ sponsoredSubscriptionItem: lineItem.sponsoredSubscriptionItem,
+ addonSubscriptionItem: lineItem.addonSubscriptionItem,
+ productName: lineItem.productName,
+ }));
+ }
+
get nextInvoice() {
return this.sub != null ? this.sub.upcomingInvoice : null;
}
- get discount() {
- return this.sub != null ? this.sub.discount : null;
+ get customerDiscount() {
+ return this.sub != null ? this.sub.customerDiscount : null;
}
get isExpired() {
@@ -168,11 +180,11 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy
}
get storageGbPrice() {
- return this.sub.plan.PasswordManager.additionalStoragePricePerGb;
+ return this.discountPrice(this.sub.plan.PasswordManager.additionalStoragePricePerGb);
}
get seatPrice() {
- return this.sub.plan.PasswordManager.seatPrice;
+ return this.discountPrice(this.sub.plan.PasswordManager.seatPrice);
}
get seats() {
@@ -183,12 +195,14 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy
return {
seatCount: this.sub.smSeats,
maxAutoscaleSeats: this.sub.maxAutoscaleSmSeats,
- seatPrice: this.sub.plan.SecretsManager.seatPrice,
+ seatPrice: this.discountPrice(this.sub.plan.SecretsManager.seatPrice),
maxAutoscaleServiceAccounts: this.sub.maxAutoscaleSmServiceAccounts,
additionalServiceAccounts:
this.sub.smServiceAccounts - this.sub.plan.SecretsManager.baseServiceAccount,
interval: this.sub.plan.isAnnual ? "year" : "month",
- additionalServiceAccountPrice: this.sub.plan.SecretsManager.additionalPricePerServiceAccount,
+ additionalServiceAccountPrice: this.discountPrice(
+ this.sub.plan.SecretsManager.additionalPricePerServiceAccount
+ ),
baseServiceAccountCount: this.sub.plan.SecretsManager.baseServiceAccount,
};
}
@@ -382,6 +396,15 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy
}
};
+ discountPrice = (price: number) => {
+ const discount =
+ !!this.customerDiscount && this.customerDiscount.active
+ ? price * (this.customerDiscount.percentOff / 100)
+ : 0;
+
+ return price - discount;
+ };
+
get showChangePlanButton() {
return this.subscription == null && this.sub.planType === PlanType.Free && !this.showChangePlan;
}
diff --git a/apps/web/src/app/billing/organizations/sm-subscribe-standalone.component.html b/apps/web/src/app/billing/organizations/sm-subscribe-standalone.component.html
index 84c74ee4282..2f3a2c08e30 100644
--- a/apps/web/src/app/billing/organizations/sm-subscribe-standalone.component.html
+++ b/apps/web/src/app/billing/organizations/sm-subscribe-standalone.component.html
@@ -4,5 +4,6 @@
[selectedPlan]="plan"
[upgradeOrganization]="false"
[showSubmitButton]="true"
+ [customerDiscount]="customerDiscount"
>
diff --git a/apps/web/src/app/billing/organizations/sm-subscribe-standalone.component.ts b/apps/web/src/app/billing/organizations/sm-subscribe-standalone.component.ts
index 2942a67560f..ebde4ab2536 100644
--- a/apps/web/src/app/billing/organizations/sm-subscribe-standalone.component.ts
+++ b/apps/web/src/app/billing/organizations/sm-subscribe-standalone.component.ts
@@ -6,6 +6,7 @@ import { InternalOrganizationServiceAbstraction } from "@bitwarden/common/admin-
import { OrganizationData } from "@bitwarden/common/admin-console/models/data/organization.data";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { SecretsManagerSubscribeRequest } from "@bitwarden/common/billing/models/request/sm-subscribe.request";
+import { BillingCustomerDiscount } from "@bitwarden/common/billing/models/response/organization-subscription.response";
import { PlanResponse } from "@bitwarden/common/billing/models/response/plan.response";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
@@ -19,6 +20,7 @@ import { secretsManagerSubscribeFormFactory } from "../shared";
export class SecretsManagerSubscribeStandaloneComponent {
@Input() plan: PlanResponse;
@Input() organization: Organization;
+ @Input() customerDiscount: BillingCustomerDiscount;
@Output() onSubscribe = new EventEmitter();
formGroup = secretsManagerSubscribeFormFactory(this.formBuilder);
diff --git a/apps/web/src/app/billing/shared/sm-subscribe.component.ts b/apps/web/src/app/billing/shared/sm-subscribe.component.ts
index 1aa6c1bccb5..85836bf17f2 100644
--- a/apps/web/src/app/billing/shared/sm-subscribe.component.ts
+++ b/apps/web/src/app/billing/shared/sm-subscribe.component.ts
@@ -3,6 +3,7 @@ import { FormBuilder, FormGroup, Validators } from "@angular/forms";
import { Subject, startWith, takeUntil } from "rxjs";
import { ControlsOf } from "@bitwarden/angular/types/controls-of";
+import { BillingCustomerDiscount } from "@bitwarden/common/billing/models/response/organization-subscription.response";
import { PlanResponse } from "@bitwarden/common/billing/models/response/plan.response";
import { ProductType } from "@bitwarden/common/enums";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
@@ -36,6 +37,7 @@ export class SecretsManagerSubscribeComponent implements OnInit, OnDestroy {
@Input() upgradeOrganization: boolean;
@Input() showSubmitButton = false;
@Input() selectedPlan: PlanResponse;
+ @Input() customerDiscount: BillingCustomerDiscount;
logo = SecretsManagerLogo;
productTypes = ProductType;
@@ -63,6 +65,15 @@ export class SecretsManagerSubscribeComponent implements OnInit, OnDestroy {
this.destroy$.complete();
}
+ discountPrice = (price: number) => {
+ const discount =
+ !!this.customerDiscount && this.customerDiscount.active
+ ? price * (this.customerDiscount.percentOff / 100)
+ : 0;
+
+ return price - discount;
+ };
+
get product() {
return this.selectedPlan.product;
}
@@ -84,8 +95,8 @@ export class SecretsManagerSubscribeComponent implements OnInit, OnDestroy {
get monthlyCostPerServiceAccount() {
return this.selectedPlan.isAnnual
- ? this.selectedPlan.SecretsManager.additionalPricePerServiceAccount / 12
- : this.selectedPlan.SecretsManager.additionalPricePerServiceAccount;
+ ? this.discountPrice(this.selectedPlan.SecretsManager.additionalPricePerServiceAccount) / 12
+ : this.discountPrice(this.selectedPlan.SecretsManager.additionalPricePerServiceAccount);
}
get maxUsers() {
@@ -98,7 +109,7 @@ export class SecretsManagerSubscribeComponent implements OnInit, OnDestroy {
get monthlyCostPerUser() {
return this.selectedPlan.isAnnual
- ? this.selectedPlan.SecretsManager.seatPrice / 12
- : this.selectedPlan.SecretsManager.seatPrice;
+ ? this.discountPrice(this.selectedPlan.SecretsManager.seatPrice) / 12
+ : this.discountPrice(this.selectedPlan.SecretsManager.seatPrice);
}
}
diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json
index 5b4b2ac8625..3f7b1d1b05e 100644
--- a/apps/web/src/locales/en/messages.json
+++ b/apps/web/src/locales/en/messages.json
@@ -7273,12 +7273,6 @@
"alreadyHaveAccount": {
"message": "Already have an account?"
},
- "customBillingStart": {
- "message": "Custom billing is not reflected. Visit the "
- },
- "customBillingEnd": {
- "message": " page for latest invoicing."
- },
"typePasskey": {
"message": "Passkey"
},
diff --git a/libs/common/src/billing/models/response/organization-subscription.response.ts b/libs/common/src/billing/models/response/organization-subscription.response.ts
index a86adbabe7c..404540c6165 100644
--- a/libs/common/src/billing/models/response/organization-subscription.response.ts
+++ b/libs/common/src/billing/models/response/organization-subscription.response.ts
@@ -1,9 +1,9 @@
import { OrganizationResponse } from "../../../admin-console/models/response/organization.response";
+import { BaseResponse } from "../../../models/response/base.response";
import {
BillingSubscriptionResponse,
BillingSubscriptionUpcomingInvoiceResponse,
- BillingCustomerDiscount,
} from "./subscription.response";
export class OrganizationSubscriptionResponse extends OrganizationResponse {
@@ -11,7 +11,7 @@ export class OrganizationSubscriptionResponse extends OrganizationResponse {
storageGb: number;
subscription: BillingSubscriptionResponse;
upcomingInvoice: BillingSubscriptionUpcomingInvoiceResponse;
- discount: BillingCustomerDiscount;
+ customerDiscount: BillingCustomerDiscount;
expiration: string;
expirationWithoutGracePeriod: string;
secretsManagerBeta: boolean;
@@ -27,10 +27,30 @@ export class OrganizationSubscriptionResponse extends OrganizationResponse {
upcomingInvoice == null
? null
: new BillingSubscriptionUpcomingInvoiceResponse(upcomingInvoice);
- const discount = this.getResponseProperty("Discount");
- this.discount = discount == null ? null : new BillingCustomerDiscount(discount);
+ const customerDiscount = this.getResponseProperty("CustomerDiscount");
+ this.customerDiscount =
+ customerDiscount == null ? null : new BillingCustomerDiscount(customerDiscount);
this.expiration = this.getResponseProperty("Expiration");
this.expirationWithoutGracePeriod = this.getResponseProperty("ExpirationWithoutGracePeriod");
this.secretsManagerBeta = this.getResponseProperty("SecretsManagerBeta");
}
}
+
+export class BillingCustomerDiscount extends BaseResponse {
+ id: string;
+ active: boolean;
+ percentOff?: number;
+
+ constructor(response: any) {
+ super(response);
+ this.id = this.getResponseProperty("Id");
+ this.active = this.getResponseProperty("Active");
+ this.percentOff = this.getResponseProperty("PercentOff");
+ }
+
+ discountPrice = (price: number) => {
+ const discount = this !== null && this.active ? price * (this.percentOff / 100) : 0;
+
+ return price - discount;
+ };
+}
diff --git a/libs/common/src/billing/models/response/subscription.response.ts b/libs/common/src/billing/models/response/subscription.response.ts
index fd84cf493d4..d6bff87adb0 100644
--- a/libs/common/src/billing/models/response/subscription.response.ts
+++ b/libs/common/src/billing/models/response/subscription.response.ts
@@ -6,7 +6,6 @@ export class SubscriptionResponse extends BaseResponse {
maxStorageGb: number;
subscription: BillingSubscriptionResponse;
upcomingInvoice: BillingSubscriptionUpcomingInvoiceResponse;
- discount: BillingCustomerDiscount;
license: any;
expiration: string;
usingInAppPurchase: boolean;
@@ -21,13 +20,11 @@ export class SubscriptionResponse extends BaseResponse {
this.usingInAppPurchase = this.getResponseProperty("UsingInAppPurchase");
const subscription = this.getResponseProperty("Subscription");
const upcomingInvoice = this.getResponseProperty("UpcomingInvoice");
- const discount = this.getResponseProperty("Discount");
this.subscription = subscription == null ? null : new BillingSubscriptionResponse(subscription);
this.upcomingInvoice =
upcomingInvoice == null
? null
: new BillingSubscriptionUpcomingInvoiceResponse(upcomingInvoice);
- this.discount = discount == null ? null : new BillingCustomerDiscount(discount);
}
}
@@ -89,14 +86,3 @@ export class BillingSubscriptionUpcomingInvoiceResponse extends BaseResponse {
this.amount = this.getResponseProperty("Amount");
}
}
-
-export class BillingCustomerDiscount extends BaseResponse {
- id: string;
- active: boolean;
-
- constructor(response: any) {
- super(response);
- this.id = this.getResponseProperty("Id");
- this.active = this.getResponseProperty("Active");
- }
-}