From da734062f099dc48deeb26bdf3fd161cc2d17f71 Mon Sep 17 00:00:00 2001 From: Cy Okeke Date: Fri, 7 Nov 2025 18:00:11 +0100 Subject: [PATCH] Fix the free for I year display --- ...nization-subscription-cloud.component.html | 13 +- ...ganization-subscription-cloud.component.ts | 126 ++++++++++++++++-- 2 files changed, 121 insertions(+), 18 deletions(-) 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 0666cca2c4b..f8157453dfc 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 @@ -18,7 +18,11 @@ >{{ "details" | i18n }}{{ "providerDiscount" | i18n: customerDiscount?.percentOff }} {{ "freeForOneYear" | i18n }} @@ -52,7 +53,7 @@ {{ i.quantity * i.amount | currency: "$" }} /{{ i.interval | i18n }} {{ calculateTotalAppliedDiscount(i.quantity * i.amount) | currency: "$" 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 fc9f8b1d986..0b50a835b69 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 @@ -52,6 +52,9 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy static readonly QUERY_PARAM_UPGRADE: string = "upgrade"; static readonly ROUTE_PARAM_ORGANIZATION_ID: string = "organizationId"; + private static readonly PRODUCT_PASSWORD_MANAGER = "passwordManager"; + private static readonly PRODUCT_SECRETS_MANAGER = "secretsManager"; + sub: OrganizationSubscriptionResponse; lineItems: BillingSubscriptionItemResponse[] = []; organizationId: string; @@ -173,8 +176,8 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy const seatPriceTotal = this.sub.plan?.SecretsManager?.seatPrice * item.quantity; item.productName = itemTotalAmount === seatPriceTotal || item.name.includes("Service Accounts") - ? "secretsManager" - : "passwordManager"; + ? OrganizationSubscriptionCloudComponent.PRODUCT_SECRETS_MANAGER + : OrganizationSubscriptionCloudComponent.PRODUCT_PASSWORD_MANAGER; return item; }) .sort(sortSubscriptionItems); @@ -216,16 +219,92 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy } get subscriptionLineItems() { - return this.lineItems.map((lineItem: BillingSubscriptionItemResponse) => ({ - name: lineItem.name, - amount: this.discountPrice(lineItem.amount, lineItem.productId), - quantity: lineItem.quantity, - interval: lineItem.interval, - sponsoredSubscriptionItem: lineItem.sponsoredSubscriptionItem, - addonSubscriptionItem: lineItem.addonSubscriptionItem, - productName: lineItem.productName, - productId: lineItem.productId, - })); + return this.lineItems.map((lineItem: BillingSubscriptionItemResponse) => { + // For SM trials with complimentary PM, only apply discount to PM products + const shouldApplyDiscount = this.shouldApplyDiscountToLineItem(lineItem); + + return { + name: lineItem.name, + amount: shouldApplyDiscount + ? this.discountPrice(lineItem.amount, lineItem.productId) + : lineItem.amount, + quantity: lineItem.quantity, + interval: lineItem.interval, + sponsoredSubscriptionItem: lineItem.sponsoredSubscriptionItem, + addonSubscriptionItem: lineItem.addonSubscriptionItem, + productName: lineItem.productName, + productId: lineItem.productId, + }; + }); + } + + private shouldApplyDiscountToLineItem(lineItem: BillingSubscriptionItemResponse): boolean { + // If no discount, don't apply + if (!this.customerDiscount?.active) { + return false; + } + + // For SM subscriptions with complimentary PM (100% discount) + // Only apply discount to PM products, not SM products + if (this.userOrg?.useSecretsManager && this.customerDiscount?.percentOff === 100) { + return ( + lineItem.productName === OrganizationSubscriptionCloudComponent.PRODUCT_PASSWORD_MANAGER + ); + } + + // For all other cases, use default discount logic + return true; + } + + shouldShowFreeForOneYear(productId: string, productName: string): boolean { + // Only show "Free for 1 year" for 100% discounts + if (!this.customerDiscount?.active || this.customerDiscount?.percentOff !== 100) { + return false; + } + + // Check if discount applies to this specific product via appliesTo array + if (this.customerDiscount?.appliesTo && this.customerDiscount.appliesTo.length > 0) { + return this.customerDiscount.appliesTo.includes(productId); + } + + // For SM subscriptions with complimentary PM (100% discount) + // When appliesTo is empty, assume it's a complimentary PM scenario + // Show "Free for 1 year" for PM products only + if ( + this.userOrg?.useSecretsManager && + this.customerDiscount?.percentOff === 100 && + productName === OrganizationSubscriptionCloudComponent.PRODUCT_PASSWORD_MANAGER + ) { + return true; + } + + return false; + } + + shouldShowDiscountStrikethrough(productId: string, productName: string): boolean { + // If no discount, don't show strikethrough + if (!this.customerDiscount?.active || !this.customerDiscount?.percentOff) { + return false; + } + + // Don't show strikethrough for Secrets Manager trial + if (this.isSecretsManagerTrial()) { + return false; + } + + // Don't show strikethrough if we should show "Free for 1 year" instead + if (this.shouldShowFreeForOneYear(productId, productName)) { + return false; + } + + // Only show strikethrough if discount applies to this specific product (for partial discounts) + if (this.customerDiscount?.appliesTo && this.customerDiscount.appliesTo.length > 0) { + return this.customerDiscount.appliesTo.includes(productId); + } + + // If appliesTo is empty or not specified, don't show strikethrough + // (we need to know which products the discount applies to) + return false; } get nextInvoice() { @@ -408,6 +487,29 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy ); } + hasPasswordManagerOnlyDiscount(): boolean { + // Only show discount badge for Password Manager-only subscriptions + // Not for Secrets Manager subscriptions with complimentary Password Manager + if (!this.customerDiscount?.percentOff || this.customerDiscount.percentOff === 0) { + return false; + } + + // If organization has Secrets Manager, don't show the badge + // (it's a bundled subscription, not a Password Manager-only discount) + if (this.userOrg?.useSecretsManager) { + return false; + } + + // Check if discount applies to any Password Manager items + const hasPasswordManagerItems = this.lineItems?.some( + (item) => + item.productName === OrganizationSubscriptionCloudComponent.PRODUCT_PASSWORD_MANAGER && + this.customerDiscount?.appliesTo?.includes(item.productId), + ); + + return hasPasswordManagerItems ?? false; + } + closeChangePlan() { this.showChangePlan = false; }