Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion internal/jobs/billing_reconciler.go
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,21 @@ var billingPaidTiers = map[string]bool{
"team": true,
}

// terminalDowngradeTier is the canonical post-state tier for ANY terminal
// Razorpay subscription event (cancel / expire / 7-day-grace-elapsed). Both
// downgrade paths land here:
//
// 1. webhook β†’ api/internal/handlers/billing.go (subscription.cancelled etc.)
// 2. reconciler β†’ billing_reconciler.go (this file, terminal status branch)
// 3. grace expiry β†’ payment_grace_terminator β†’ api/internal/handlers/internal_terminate.go
//
// All three MUST write the same tier or two teams with the same canonical
// outcome end up on different post-states (D28 F1, BugBash 2026-05-21).
// "hobby" is the floor (lowest PAID tier); "free" is the 24h-TTL ephemeral
// tier and is NOT a valid terminal target β€” see the long-form rationale in
// the rzpStatusClassTerminal branch below.
const terminalDowngradeTier = "hobby"

// ── worker-local planIDToTier ─────────────────────────────────────────────────

// Razorpay plan-id env-var names. These MUST match the names the api reads in
Expand Down Expand Up @@ -960,7 +975,13 @@ func (w *BillingReconcilerWorker) Work(ctx context.Context, job *river.Job[Billi
// PaidCount. (PaidCount==0 only means no *successful* charge
// was recorded β€” e.g. a failed first charge then cancellation;
// it is not a signal that resources should become ephemeral.)
const terminalDowngradeTier = "hobby"
//
// BugBash #9 / D28 F1 (2026-05-21): the api side
// /internal/teams/:id/terminate must also land on
// terminalDowngradeTier (was historically "free"). The
// constant is package-level so a coverage test can assert
// it (TestBillingReconciler_TerminalDowngradeTierIsHobby)
// and so future readers see the cross-repo contract.
targetTier := terminalDowngradeTier
slog.Warn("billing.reconciler.tier_corrected",
"team_id", team.id,
Expand Down
5 changes: 5 additions & 0 deletions internal/jobs/export_billing_reconciler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@ func BillingTierRank(tier string) int {
return billingTierRank(tier)
}

// TerminalDowngradeTier exports the canonical terminal-subscription tier
// the reconciler downgrades to. Used by TestBillingReconciler_TerminalDowngradeTierIsHobby
// to lock the value (D28 F1, BugBash 2026-05-21).
const TerminalDowngradeTier = terminalDowngradeTier

// ErrReconcilerCircuitOpen exports errReconcilerCircuitOpen for circuit-open tests.
var ErrReconcilerCircuitOpen = errReconcilerCircuitOpen

Expand Down
Loading