Files
pulse/frontend-modern/src/utils/pricingHandoff.ts
T
rcourtman 81b31e4d3b Remove monitored-system volume caps
Retire runtime/API/UI monitored-system volume enforcement now that infrastructure monitoring is no longer capped.

Keep only legacy metadata scrubbing and purchase-start compatibility for old max_monitored_systems references.

Rename the remaining preview surface to monitored-system impact and make previews explanatory rather than save-blocking.

Update subsystem contracts and RA7 evidence for the caps-retired invariant.
2026-05-05 12:59:59 +01:00

395 lines
14 KiB
TypeScript

import {
isExternalUpgradeHref,
resolveUpgradeDestination,
type UpgradeDestination,
} from '@/utils/upgradeNavigation';
const DEFAULT_PUBLIC_PRICING_URL =
'https://pulserelay.pro/pricing?utm_source=pulse&utm_medium=app&utm_campaign=upgrade';
const DEFAULT_CLOUD_ACCOUNT_PORTAL_URL = 'https://cloud.pulserelay.pro/portal';
export const SELF_HOSTED_PURCHASE_START_PATH = '/auth/license-purchase-start';
export const SELF_HOSTED_PRO_BILLING_ROUTE = '/settings/system/billing';
export const SELF_HOSTED_PRO_BILLING_PLAN_ROUTE = `${SELF_HOSTED_PRO_BILLING_ROUTE}/plan`;
export const SELF_HOSTED_PRO_BILLING_USAGE_ROUTE = `${SELF_HOSTED_PRO_BILLING_ROUTE}/usage`;
export const SELF_HOSTED_PRO_BILLING_PLAN_SECTION_ID = 'pulse-pro-plan';
export const SELF_HOSTED_PRO_BILLING_USAGE_SECTION_ID = 'pulse-pro-usage';
export const SELF_HOSTED_PRO_BILLING_RECOVERY_SECTION_ID = 'pulse-pro-recovery';
export const SELF_HOSTED_PRO_BILLING_DETAILS_QUERY_PARAM = 'details';
export const SELF_HOSTED_PRO_BILLING_USAGE_DETAILS_QUERY_PARAM =
SELF_HOSTED_PRO_BILLING_DETAILS_QUERY_PARAM;
export const SELF_HOSTED_PRO_BILLING_PLAN_DETAILS_QUERY_PARAM =
SELF_HOSTED_PRO_BILLING_DETAILS_QUERY_PARAM;
export const SELF_HOSTED_PRO_BILLING_PLAN_INTENT_QUERY_PARAM = 'intent';
export const SELF_HOSTED_PRO_BILLING_PURCHASE_QUERY_PARAM = 'purchase';
export const SELF_HOSTED_PRO_BILLING_COUNTING_RULES_DETAIL = 'counting-rules';
export const SELF_HOSTED_PRO_BILLING_RECOVERY_DETAIL = 'recovery';
export const SELF_HOSTED_PRO_BILLING_PLAN_SELECTION_INTENT = 'self_hosted_plan';
export const SELF_HOSTED_PRO_BILLING_PURCHASE_ACTIVATED = 'activated';
export const SELF_HOSTED_PRO_BILLING_PURCHASE_CANCELLED = 'cancelled';
export const SELF_HOSTED_PRO_BILLING_PURCHASE_EXPIRED = 'expired';
export const SELF_HOSTED_PRO_BILLING_PURCHASE_FAILED = 'failed';
export const SELF_HOSTED_PRO_BILLING_PURCHASE_UNAVAILABLE = 'unavailable';
export type SelfHostedBillingSection = 'plan' | 'usage';
export type SelfHostedBillingUsageDetail = typeof SELF_HOSTED_PRO_BILLING_COUNTING_RULES_DETAIL;
export type SelfHostedBillingPlanDetail = typeof SELF_HOSTED_PRO_BILLING_RECOVERY_DETAIL;
export type SelfHostedBillingDetail = SelfHostedBillingUsageDetail | SelfHostedBillingPlanDetail;
export type SelfHostedBillingPlanIntent = typeof SELF_HOSTED_PRO_BILLING_PLAN_SELECTION_INTENT;
export type SelfHostedBillingPurchaseArrival =
| typeof SELF_HOSTED_PRO_BILLING_PURCHASE_ACTIVATED
| typeof SELF_HOSTED_PRO_BILLING_PURCHASE_CANCELLED
| typeof SELF_HOSTED_PRO_BILLING_PURCHASE_EXPIRED
| typeof SELF_HOSTED_PRO_BILLING_PURCHASE_FAILED
| typeof SELF_HOSTED_PRO_BILLING_PURCHASE_UNAVAILABLE;
export const SELF_HOSTED_PRO_BILLING_PLAN_HREF = SELF_HOSTED_PRO_BILLING_PLAN_ROUTE;
export const SELF_HOSTED_PRO_BILLING_USAGE_HREF = SELF_HOSTED_PRO_BILLING_USAGE_ROUTE;
export const SELF_HOSTED_PRO_BILLING_PLAN_RECOVERY_HREF = `${SELF_HOSTED_PRO_BILLING_PLAN_ROUTE}?${SELF_HOSTED_PRO_BILLING_PLAN_DETAILS_QUERY_PARAM}=${SELF_HOSTED_PRO_BILLING_RECOVERY_DETAIL}`;
export const SELF_HOSTED_PRO_BILLING_USAGE_COUNTING_RULES_HREF = `${SELF_HOSTED_PRO_BILLING_USAGE_ROUTE}?${SELF_HOSTED_PRO_BILLING_USAGE_DETAILS_QUERY_PARAM}=${SELF_HOSTED_PRO_BILLING_COUNTING_RULES_DETAIL}`;
export const SELF_HOSTED_PRO_BILLING_PLAN_SELECTION_HREF = `${SELF_HOSTED_PRO_BILLING_PLAN_ROUTE}?${SELF_HOSTED_PRO_BILLING_PLAN_INTENT_QUERY_PARAM}=${SELF_HOSTED_PRO_BILLING_PLAN_SELECTION_INTENT}`;
const IN_PRODUCT_PRICING_DESTINATIONS: Record<string, string> = {
self_hosted_plan: SELF_HOSTED_PRO_BILLING_PLAN_SELECTION_HREF,
// Paid self-hosted feature keys: route to the owned billing plan page instead
// of the Pulse Account purchase-start handoff, which fails for local instances
// without PublicURL.
mobile_app: SELF_HOSTED_PRO_BILLING_PLAN_HREF,
push_notifications: SELF_HOSTED_PRO_BILLING_PLAN_HREF,
ai_alerts: SELF_HOSTED_PRO_BILLING_PLAN_HREF,
ai_autofix: SELF_HOSTED_PRO_BILLING_PLAN_HREF,
relay: SELF_HOSTED_PRO_BILLING_PLAN_HREF,
rbac: SELF_HOSTED_PRO_BILLING_PLAN_HREF,
audit_logging: SELF_HOSTED_PRO_BILLING_PLAN_HREF,
advanced_reporting: SELF_HOSTED_PRO_BILLING_PLAN_HREF,
agent_profiles: SELF_HOSTED_PRO_BILLING_PLAN_HREF,
long_term_metrics: SELF_HOSTED_PRO_BILLING_PLAN_HREF,
};
const RETIRED_TRIAL_PRICING_FEATURES = new Set(['trial_expired']);
const INTERNAL_HREF_BASE = 'https://pulse.invalid';
function normalizeSettingsLikePath(pathname: string): string {
const normalized = pathname.trim();
if (!normalized) return pathname;
if (normalized.length > 1 && normalized.endsWith('/')) {
return normalized.replace(/\/+$/, '');
}
return normalized;
}
function normalizeSearch(search: string): string {
return search.startsWith('?') ? search.slice(1) : search;
}
function normalizeHash(hash: string): string {
const normalized = hash.trim();
return normalized.startsWith('#') ? normalized.slice(1) : normalized;
}
function billingSearch(search: string): URLSearchParams {
return new URLSearchParams(normalizeSearch(search));
}
function isSelfHostedBillingPath(pathname: string): boolean {
const normalized = normalizeSettingsLikePath(pathname);
return (
normalized === SELF_HOSTED_PRO_BILLING_ROUTE ||
normalized === SELF_HOSTED_PRO_BILLING_PLAN_ROUTE ||
normalized === SELF_HOSTED_PRO_BILLING_USAGE_ROUTE ||
normalized.startsWith(`${SELF_HOSTED_PRO_BILLING_ROUTE}/`)
);
}
function normalizeFeatureKey(feature: string | null | undefined): string | undefined {
const normalized = feature?.trim();
return normalized ? normalized : undefined;
}
export function getSelfHostedPurchaseStartUrl(
feature?: string | null,
searchParams?: URLSearchParams,
): string {
const url = new URL(SELF_HOSTED_PURCHASE_START_PATH, INTERNAL_HREF_BASE);
if (searchParams) {
for (const [key, value] of searchParams.entries()) {
const normalizedValue = value.trim();
if (normalizedValue) {
url.searchParams.set(key, normalizedValue);
}
}
}
const normalizedFeature = normalizeFeatureKey(feature);
if (normalizedFeature) {
url.searchParams.set('feature', normalizedFeature);
}
return `${url.pathname}${url.search}`;
}
export function resolveSelfHostedPurchaseStartDestination(
feature?: string | null,
searchParams?: URLSearchParams,
): UpgradeDestination {
return resolveUpgradeDestination(getSelfHostedPurchaseStartUrl(feature, searchParams), {
hardNavigation: true,
newTab: true,
preserveOpener: true,
});
}
export function getInProductPricingDestination(
feature: string | null | undefined,
): string | undefined {
const normalizedFeature = normalizeFeatureKey(feature);
if (!normalizedFeature) return undefined;
return IN_PRODUCT_PRICING_DESTINATIONS[normalizedFeature];
}
export function isRetiredPricingFeature(feature: string | null | undefined): boolean {
const normalizedFeature = normalizeFeatureKey(feature);
return normalizedFeature ? RETIRED_TRIAL_PRICING_FEATURES.has(normalizedFeature) : false;
}
export function getPublicPricingUrl(feature?: string | null): string {
const url = new URL(DEFAULT_PUBLIC_PRICING_URL);
const normalizedFeature = normalizeFeatureKey(feature);
if (normalizedFeature) {
url.searchParams.set('feature', normalizedFeature);
}
return url.toString();
}
export function getCloudAccountPortalUrl(): string {
return DEFAULT_CLOUD_ACCOUNT_PORTAL_URL;
}
export function getUpgradeFallbackDestination(feature?: string | null): string | undefined {
if (isRetiredPricingFeature(feature)) return undefined;
if (normalizeFeatureKey(feature) === 'cloud') {
return getCloudAccountPortalUrl();
}
return getInProductPricingDestination(feature) || getSelfHostedPurchaseStartUrl(feature);
}
export function getSelfHostedBillingUsageDetail(
search: string,
): SelfHostedBillingUsageDetail | null {
const detail = billingSearch(search)
.get(SELF_HOSTED_PRO_BILLING_USAGE_DETAILS_QUERY_PARAM)
?.trim();
return detail === SELF_HOSTED_PRO_BILLING_COUNTING_RULES_DETAIL ? detail : null;
}
export function getSelfHostedBillingPlanDetail(search: string): SelfHostedBillingPlanDetail | null {
const detail = billingSearch(search)
.get(SELF_HOSTED_PRO_BILLING_PLAN_DETAILS_QUERY_PARAM)
?.trim();
return detail === SELF_HOSTED_PRO_BILLING_RECOVERY_DETAIL ? detail : null;
}
export function getSelfHostedBillingPlanIntent(search: string): SelfHostedBillingPlanIntent | null {
const intent = billingSearch(search).get(SELF_HOSTED_PRO_BILLING_PLAN_INTENT_QUERY_PARAM)?.trim();
if (intent === SELF_HOSTED_PRO_BILLING_PLAN_SELECTION_INTENT) {
return SELF_HOSTED_PRO_BILLING_PLAN_SELECTION_INTENT;
}
return null;
}
export function getSelfHostedBillingPurchaseArrival(
search: string,
): SelfHostedBillingPurchaseArrival | null {
const purchase = billingSearch(search).get(SELF_HOSTED_PRO_BILLING_PURCHASE_QUERY_PARAM)?.trim();
switch (purchase) {
case SELF_HOSTED_PRO_BILLING_PURCHASE_ACTIVATED:
case SELF_HOSTED_PRO_BILLING_PURCHASE_CANCELLED:
case SELF_HOSTED_PRO_BILLING_PURCHASE_EXPIRED:
case SELF_HOSTED_PRO_BILLING_PURCHASE_FAILED:
case SELF_HOSTED_PRO_BILLING_PURCHASE_UNAVAILABLE:
return purchase;
default:
return null;
}
}
export function getSelfHostedBillingHref(
section: SelfHostedBillingSection,
options: {
detail?: SelfHostedBillingDetail | null;
intent?: SelfHostedBillingPlanIntent | null;
purchase?: SelfHostedBillingPurchaseArrival | null;
} = {},
): string {
const baseRoute =
section === 'usage' ? SELF_HOSTED_PRO_BILLING_USAGE_ROUTE : SELF_HOSTED_PRO_BILLING_PLAN_ROUTE;
const params = new URLSearchParams();
if (section === 'usage' && options.detail === SELF_HOSTED_PRO_BILLING_COUNTING_RULES_DETAIL) {
params.set(
SELF_HOSTED_PRO_BILLING_USAGE_DETAILS_QUERY_PARAM,
SELF_HOSTED_PRO_BILLING_COUNTING_RULES_DETAIL,
);
}
if (section === 'plan' && options.intent === SELF_HOSTED_PRO_BILLING_PLAN_SELECTION_INTENT) {
params.set(
SELF_HOSTED_PRO_BILLING_PLAN_INTENT_QUERY_PARAM,
SELF_HOSTED_PRO_BILLING_PLAN_SELECTION_INTENT,
);
}
if (section === 'plan' && options.detail === SELF_HOSTED_PRO_BILLING_RECOVERY_DETAIL) {
params.set(
SELF_HOSTED_PRO_BILLING_PLAN_DETAILS_QUERY_PARAM,
SELF_HOSTED_PRO_BILLING_RECOVERY_DETAIL,
);
}
if (options.purchase) {
params.set(SELF_HOSTED_PRO_BILLING_PURCHASE_QUERY_PARAM, options.purchase);
}
const search = params.toString();
return `${baseRoute}${search ? `?${search}` : ''}`;
}
export function resolveSelfHostedBillingSection(
pathname: string,
search = '',
hash = '',
): SelfHostedBillingSection {
const normalizedPath = normalizeSettingsLikePath(pathname);
if (normalizedPath === SELF_HOSTED_PRO_BILLING_USAGE_ROUTE) {
return 'usage';
}
if (normalizedPath === SELF_HOSTED_PRO_BILLING_PLAN_ROUTE) {
return 'plan';
}
if (normalizedPath !== SELF_HOSTED_PRO_BILLING_ROUTE) {
return 'plan';
}
const normalizedHash = normalizeHash(hash);
if (normalizedHash === SELF_HOSTED_PRO_BILLING_USAGE_SECTION_ID) {
return 'usage';
}
if (normalizedHash === SELF_HOSTED_PRO_BILLING_PLAN_SECTION_ID) {
return 'plan';
}
if (normalizedHash === SELF_HOSTED_PRO_BILLING_RECOVERY_SECTION_ID) {
return 'plan';
}
if (getSelfHostedBillingUsageDetail(search) === SELF_HOSTED_PRO_BILLING_COUNTING_RULES_DETAIL) {
return 'usage';
}
if (getSelfHostedBillingPlanDetail(search) === SELF_HOSTED_PRO_BILLING_RECOVERY_DETAIL) {
return 'plan';
}
return 'plan';
}
export function resolveCanonicalSelfHostedBillingHref(
pathname: string,
search = '',
hash = '',
): string | null {
const normalizedPath = normalizeSettingsLikePath(pathname);
if (!isSelfHostedBillingPath(normalizedPath)) {
return null;
}
const section = resolveSelfHostedBillingSection(normalizedPath, search, hash);
const normalizedHash = normalizeHash(hash);
const usageDetail =
section === 'usage' &&
getSelfHostedBillingUsageDetail(search) === SELF_HOSTED_PRO_BILLING_COUNTING_RULES_DETAIL
? SELF_HOSTED_PRO_BILLING_COUNTING_RULES_DETAIL
: null;
const planIntent = section === 'plan' ? getSelfHostedBillingPlanIntent(search) : null;
const planDetail =
section === 'plan' &&
(getSelfHostedBillingPlanDetail(search) === SELF_HOSTED_PRO_BILLING_RECOVERY_DETAIL ||
normalizedHash === SELF_HOSTED_PRO_BILLING_RECOVERY_SECTION_ID)
? SELF_HOSTED_PRO_BILLING_RECOVERY_DETAIL
: null;
const purchase = section === 'plan' ? getSelfHostedBillingPurchaseArrival(search) : null;
return getSelfHostedBillingHref(section, {
detail: section === 'usage' ? usageDetail : planDetail,
intent: planIntent,
purchase,
});
}
export function scopeSelfHostedBillingDestination(
destination: UpgradeDestination,
section: SelfHostedBillingSection,
options: {
detail?: SelfHostedBillingDetail | null;
intent?: SelfHostedBillingPlanIntent | null;
purchase?: SelfHostedBillingPurchaseArrival | null;
} = {},
): UpgradeDestination {
if (destination.external) {
return destination;
}
const url = new URL(destination.href, INTERNAL_HREF_BASE);
if (!isSelfHostedBillingPath(url.pathname)) {
return destination;
}
return {
...destination,
href: getSelfHostedBillingHref(section, options),
};
}
export function getPricingRouteDestination(search: string): string {
const params = new URLSearchParams(search.startsWith('?') ? search.slice(1) : search);
const feature = params.get('feature');
if (isRetiredPricingFeature(feature)) {
return SELF_HOSTED_PRO_BILLING_PLAN_HREF;
}
const inProductDestination = getInProductPricingDestination(feature);
if (inProductDestination) {
return inProductDestination;
}
if (normalizeFeatureKey(feature)) {
return getSelfHostedPurchaseStartUrl(feature, params);
}
const url = new URL(DEFAULT_PUBLIC_PRICING_URL);
for (const [key, value] of params.entries()) {
const normalizedValue = value.trim();
if (normalizedValue) {
url.searchParams.set(key, normalizedValue);
}
}
return url.toString();
}
export function isExternalPricingDestination(destination: string): boolean {
return isExternalUpgradeHref(destination);
}
export function isSelfHostedPurchaseStartDestination(destination: string): boolean {
if (isExternalUpgradeHref(destination)) {
return false;
}
try {
return new URL(destination, INTERNAL_HREF_BASE).pathname === SELF_HOSTED_PURCHASE_START_PATH;
} catch {
return false;
}
}
export function handoffToExternalPricing(destination: string): void {
window.location.replace(destination);
}