mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-19 10:33:29 +00:00
Preserve grandfathered recurring billing baselines
This commit is contained in:
parent
bd8b2efd1b
commit
6d65571105
3 changed files with 57 additions and 1 deletions
|
|
@ -216,6 +216,14 @@ Insights`, rather than reviving generic `AI Patrol` or `AI ... analysis`
|
|||
monitored-system and guest capacity while the subscription remains
|
||||
continuous, and only new v6 retail purchases or post-cancellation re-entry
|
||||
may take the current Pro/Pro+ caps.
|
||||
7. Keep persisted billing baselines and live recurring continuity distinct:
|
||||
`pkg/licensing/billing_state_normalization.go` must store the canonical
|
||||
monitored-system billing baseline for recognized grandfathered recurring
|
||||
v5/v1 Stripe plans so webhook persistence and admin-visible hosted billing
|
||||
state stay deterministic, while `pkg/licensing/database_source.go`,
|
||||
`pkg/licensing/models.go`, and downstream runtime entitlement evaluation
|
||||
must strip that stored cap before enforcement so active recurring
|
||||
grandfathered continuity remains uncapped until cancellation.
|
||||
|
||||
## Current State
|
||||
|
||||
|
|
@ -1379,6 +1387,16 @@ canonical Cloud/MSP limit rule: when paid state is granted, billing-state
|
|||
writes must persist authoritative `limits.max_monitored_systems` derived from canonical
|
||||
plan resolution, and when paid state is revoked they must clear those stored
|
||||
limits instead of preserving stale paid capacity.
|
||||
Grandfathered recurring v5/v1 continuity is the explicit stored-state
|
||||
exception inside that same boundary. `pkg/licensing/billing_state_normalization.go`
|
||||
must persist the canonical billing baseline for recognized grandfathered
|
||||
recurring Stripe plans even though the live entitlement contract is uncapped,
|
||||
so webhook persistence, hosted billing state, and admin inspection stay
|
||||
deterministic instead of leaking an internal `0 == unlimited` convention into
|
||||
saved billing records. `pkg/licensing/database_source.go`,
|
||||
`pkg/licensing/models.go`, and downstream entitlement evaluation must then
|
||||
strip that stored monitored-system cap back out before runtime enforcement, so
|
||||
continuous grandfathered recurring customers stay uncapped until cancellation.
|
||||
That same monitored-system entitlement boundary also owns the shared operator
|
||||
warning copy: the limit banner and migration guidance must present the counted
|
||||
surface as monitored systems, not drift back into agent-install language while
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ func NormalizeBillingState(state *BillingState) *BillingState {
|
|||
normalized.Limits = map[string]int64{}
|
||||
normalized.MetersEnabled = []string{}
|
||||
default:
|
||||
if limit, known := CloudPlanMonitoredSystemLimits[normalized.PlanVersion]; known {
|
||||
if limit, ok := billingStateStoredMonitoredSystemLimit(normalized.PlanVersion); ok {
|
||||
normalized.Limits[MaxMonitoredSystemsLicenseGateKey] = int64(limit)
|
||||
}
|
||||
}
|
||||
|
|
@ -76,6 +76,18 @@ func NormalizeBillingState(state *BillingState) *BillingState {
|
|||
return normalized
|
||||
}
|
||||
|
||||
func billingStateStoredMonitoredSystemLimit(planVersion string) (int, bool) {
|
||||
planVersion = CanonicalizePlanVersion(planVersion)
|
||||
if IsGrandfatheredRecurringV5PlanVersion(planVersion) {
|
||||
return UnknownPlanDefaultMonitoredSystemLimit, true
|
||||
}
|
||||
limit, known := CloudPlanMonitoredSystemLimits[planVersion]
|
||||
if !known || limit <= 0 {
|
||||
return 0, false
|
||||
}
|
||||
return limit, true
|
||||
}
|
||||
|
||||
func IsValidBillingSubscriptionState(state SubscriptionState) bool {
|
||||
switch state {
|
||||
case SubStateTrial,
|
||||
|
|
|
|||
|
|
@ -306,6 +306,32 @@ func TestNormalizeBillingState_CanonicalizesCloudPlanVersionAndLimits(t *testing
|
|||
}
|
||||
}
|
||||
|
||||
func TestNormalizeBillingState_GrandfatheredRecurringPlanUsesStoredBillingBaseline(t *testing.T) {
|
||||
state := &BillingState{
|
||||
PlanVersion: " v5_pro_monthly_grandfathered ",
|
||||
Limits: map[string]int64{
|
||||
"max_monitored_systems": 0,
|
||||
"max_nodes": 99,
|
||||
"max_guests": 50,
|
||||
},
|
||||
SubscriptionState: SubStateActive,
|
||||
}
|
||||
|
||||
normalized := NormalizeBillingState(state)
|
||||
if normalized.PlanVersion != "v5_pro_monthly_grandfathered" {
|
||||
t.Fatalf("plan_version=%q, want %q", normalized.PlanVersion, "v5_pro_monthly_grandfathered")
|
||||
}
|
||||
if got := normalized.Limits["max_monitored_systems"]; got != UnknownPlanDefaultMonitoredSystemLimit {
|
||||
t.Fatalf("limits[max_monitored_systems]=%d, want %d", got, UnknownPlanDefaultMonitoredSystemLimit)
|
||||
}
|
||||
if got := normalized.Limits["max_guests"]; got != 50 {
|
||||
t.Fatalf("limits[max_guests]=%d, want %d", got, 50)
|
||||
}
|
||||
if _, hasOld := normalized.Limits["max_nodes"]; hasOld {
|
||||
t.Fatal("expected max_nodes to be deleted during normalization")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizeBillingState_PreservesNonCloudPlanLimits(t *testing.T) {
|
||||
state := &BillingState{
|
||||
PlanVersion: "pro-v2",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue