diff --git a/server/channels/api4/cloud.go b/server/channels/api4/cloud.go index fc1c6ce33a..1c448a0efd 100644 --- a/server/channels/api4/cloud.go +++ b/server/channels/api4/cloud.go @@ -103,6 +103,7 @@ func getSubscription(c *Context, w http.ResponseWriter, r *http.Request) { DNS: "", LastInvoice: &model.Invoice{}, DelinquentSince: subscription.DelinquentSince, + BillingType: "", } } diff --git a/server/model/cloud.go b/server/model/cloud.go index a2176f8bd9..1efad1878c 100644 --- a/server/model/cloud.go +++ b/server/model/cloud.go @@ -179,6 +179,7 @@ type Subscription struct { DelinquentSince *int64 `json:"delinquent_since"` OriginallyLicensedSeats int `json:"originally_licensed_seats"` ComplianceBlocked string `json:"compliance_blocked"` + BillingType string `json:"billing_type"` } // Subscription History model represents true up event in a yearly subscription diff --git a/webapp/channels/src/components/admin_console/billing/billing_subscriptions/to_yearly_nudge_banner.test.tsx b/webapp/channels/src/components/admin_console/billing/billing_subscriptions/to_yearly_nudge_banner.test.tsx index e9061333a5..1fd7456656 100644 --- a/webapp/channels/src/components/admin_console/billing/billing_subscriptions/to_yearly_nudge_banner.test.tsx +++ b/webapp/channels/src/components/admin_console/billing/billing_subscriptions/to_yearly_nudge_banner.test.tsx @@ -197,6 +197,58 @@ describe('ToYearlyNudgeBannerDismissable', () => { expect(() => screen.getByTestId('cloud-pro-monthly-deprecation-announcement-bar')).toThrow(); }); + + test('should NOT show when subscription has billing type of internal', () => { + const state = JSON.parse(JSON.stringify(initialState)); + state.entities.users.profiles = { + current_user_id: {roles: 'system_admin'}, + }; + state.entities.cloud = { + subscription: { + product_id: 'prod_professional', + is_free_trial: 'false', + trial_end_at: 1, + billing_type: 'internal', + }, + products: { + prod_professional: { + id: 'prod_professional', + sku: CloudProducts.PROFESSIONAL, + recurring_interval: RecurringIntervals.MONTH, + }, + }, + }; + + renderWithIntlAndStore(, state); + + expect(() => screen.getByTestId('cloud-pro-monthly-deprecation-announcement-bar')).toThrow(); + }); + + test('should NOT show when subscription has billing type of licensed', () => { + const state = JSON.parse(JSON.stringify(initialState)); + state.entities.users.profiles = { + current_user_id: {roles: 'system_admin'}, + }; + state.entities.cloud = { + subscription: { + product_id: 'prod_professional', + is_free_trial: 'false', + trial_end_at: 1, + billing_type: 'licensed', + }, + products: { + prod_professional: { + id: 'prod_professional', + sku: CloudProducts.PROFESSIONAL, + recurring_interval: RecurringIntervals.MONTH, + }, + }, + }; + + renderWithIntlAndStore(, state); + + expect(() => screen.getByTestId('cloud-pro-monthly-deprecation-announcement-bar')).toThrow(); + }); }); describe('ToYearlyNudgeBanner', () => { @@ -241,6 +293,51 @@ describe('ToYearlyNudgeBanner', () => { renderWithIntlAndStore(, state); + expect(() => screen.getByTestId('cloud-pro-monthly-deprecation-alert-banner')).toThrow(); + }); + test('should NOT show when subscription has billing type of internal', () => { + const state = JSON.parse(JSON.stringify(initialState)); + state.entities.cloud = { + subscription: { + product_id: 'prod_professional', + is_free_trial: 'false', + trial_end_at: 1, + billing_type: 'internal', + }, + products: { + prod_professional: { + id: 'prod_professional', + sku: CloudProducts.PROFESSIONAL, + recurring_interval: RecurringIntervals.MONTH, + }, + }, + }; + + renderWithIntlAndStore(, state); + + expect(() => screen.getByTestId('cloud-pro-monthly-deprecation-alert-banner')).toThrow(); + }); + + test('should NOT show when subscription has billing type of licensed', () => { + const state = JSON.parse(JSON.stringify(initialState)); + state.entities.cloud = { + subscription: { + product_id: 'prod_professional', + is_free_trial: 'false', + trial_end_at: 1, + billing_type: 'licensed', + }, + products: { + prod_professional: { + id: 'prod_professional', + sku: CloudProducts.PROFESSIONAL, + recurring_interval: RecurringIntervals.MONTH, + }, + }, + }; + + renderWithIntlAndStore(, state); + expect(() => screen.getByTestId('cloud-pro-monthly-deprecation-alert-banner')).toThrow(); }); }); diff --git a/webapp/channels/src/components/admin_console/billing/billing_subscriptions/to_yearly_nudge_banner.tsx b/webapp/channels/src/components/admin_console/billing/billing_subscriptions/to_yearly_nudge_banner.tsx index f2eac86e69..e5640663b0 100644 --- a/webapp/channels/src/components/admin_console/billing/billing_subscriptions/to_yearly_nudge_banner.tsx +++ b/webapp/channels/src/components/admin_console/billing/billing_subscriptions/to_yearly_nudge_banner.tsx @@ -11,12 +11,12 @@ import useOpenCloudPurchaseModal from 'components/common/hooks/useOpenCloudPurch import useOpenSalesLink from 'components/common/hooks/useOpenSalesLink'; import AnnouncementBar from 'components/announcement_bar/default_announcement_bar'; -import {getSubscriptionProduct as selectSubscriptionProduct} from 'mattermost-redux/selectors/entities/cloud'; +import {getSubscriptionProduct as selectSubscriptionProduct, getCloudSubscription as selectCloudSubscription} from 'mattermost-redux/selectors/entities/cloud'; import {getCurrentUser, isCurrentUserSystemAdmin} from 'mattermost-redux/selectors/entities/users'; import {savePreferences} from 'mattermost-redux/actions/preferences'; import {get as getPreference} from 'mattermost-redux/selectors/entities/preferences'; -import {AnnouncementBarTypes, CloudBanners, CloudProducts, Preferences, RecurringIntervals} from 'utils/constants'; +import {AnnouncementBarTypes, CloudBanners, CloudProducts, Preferences, RecurringIntervals, CloudBillingTypes} from 'utils/constants'; import {t} from 'utils/i18n'; import {GlobalState} from '@mattermost/types/store'; @@ -53,6 +53,7 @@ const ToYearlyNudgeBannerDismissable = () => { const show = snoozeInfo.show; const currentUser = useSelector(getCurrentUser); + const subscription = useSelector(selectCloudSubscription); const isAdmin = useSelector(isCurrentUserSystemAdmin); const product = useSelector(selectSubscriptionProduct); const currentProductProfessional = product?.sku === CloudProducts.PROFESSIONAL; @@ -139,6 +140,10 @@ const ToYearlyNudgeBannerDismissable = () => { return null; } + if (subscription?.billing_type === CloudBillingTypes.INTERNAL || subscription?.billing_type === CloudBillingTypes.LICENSED) { + return null; + } + const message = ( { showCloseButton={daysToProMonthlyEnd > 10} onButtonClick={() => openPurchaseModal({trackingLocation: 'to_yearly_nudge_annoucement_bar'})} modalButtonText={t('cloud_billing.nudge_to_yearly.learn_more')} - modalButtonDefaultText='Learn more' + modalButtonDefaultText='Update billing' message={message} showLinkAsButton={true} handleClose={showBanner} @@ -173,6 +178,7 @@ const ToYearlyNudgeBanner = () => { const [openSalesLink] = useOpenSalesLink(); const openPurchaseModal = useOpenCloudPurchaseModal({}); + const subscription = useSelector(selectCloudSubscription); const product = useSelector(selectSubscriptionProduct); const currentProductProfessional = product?.sku === CloudProducts.PROFESSIONAL; const currentProductIsMonthly = product?.recurring_interval === RecurringIntervals.MONTH; @@ -182,6 +188,10 @@ const ToYearlyNudgeBanner = () => { return null; } + if (subscription?.billing_type === CloudBillingTypes.INTERNAL || subscription?.billing_type === CloudBillingTypes.LICENSED) { + return null; + } + const now = moment(Date.now()); const proMonthlyEndDate = moment(cloudProMonthlyCloseMoment, 'YYYYMMDD'); const daysToProMonthlyEnd = proMonthlyEndDate.diff(now, 'days'); diff --git a/webapp/channels/src/i18n/en.json b/webapp/channels/src/i18n/en.json index c5af857223..4d1aee8c3f 100644 --- a/webapp/channels/src/i18n/en.json +++ b/webapp/channels/src/i18n/en.json @@ -3056,7 +3056,7 @@ "cloud_billing.nudge_to_yearly.announcement_bar": "Monthly billing will be discontinued in {days} days . Switch to annual billing", "cloud_billing.nudge_to_yearly.contact_sales": "Contact sales", "cloud_billing.nudge_to_yearly.description": "Monthly billing will be discontinued on {date}. To keep your workspace, switch to annual billing.", - "cloud_billing.nudge_to_yearly.learn_more": "Learn more", + "cloud_billing.nudge_to_yearly.learn_more": "Update billing", "cloud_billing.nudge_to_yearly.title": "Action required: Switch to annual billing to keep your workspace.", "cloud_delinquency.banner.buttonText": "Update billing now", "cloud_delinquency.banner.end_user_notify_admin_button": "Notify admin", diff --git a/webapp/channels/src/utils/constants.tsx b/webapp/channels/src/utils/constants.tsx index 7f1dce9244..91b82df939 100644 --- a/webapp/channels/src/utils/constants.tsx +++ b/webapp/channels/src/utils/constants.tsx @@ -504,6 +504,11 @@ export const CloudProducts = { LEGACY: 'cloud-legacy', }; +export const CloudBillingTypes = { + INTERNAL: 'internal', + LICENSED: 'licensed', +}; + export const SelfHostedProducts = { STARTER: 'starter', PROFESSIONAL: 'professional', diff --git a/webapp/platform/types/src/cloud.ts b/webapp/platform/types/src/cloud.ts index 74b7195ab2..9cdb28a01c 100644 --- a/webapp/platform/types/src/cloud.ts +++ b/webapp/platform/types/src/cloud.ts @@ -41,6 +41,7 @@ export type Subscription = { is_free_trial: string; delinquent_since?: number; compliance_blocked?: string; + billing_type?: string; } export type Product = {