Skip to content

fix(billing): re-apply 5 lost C.2 review nits from PR #3743 (warn-on-unknown-priceId + hardening) #3757

@PierreBrisorgueil

Description

@PierreBrisorgueil

Context

PR #3743 (fix(billing): resolve plan via priceId map, not price.metadata.planId) was merged to devkit master with 5 unfixed Copilot review findings. A controller-side fix commit (11b00505) was prepared locally but never pushed to origin/feat/resolveplan-priceid-map — the push silently appeared to succeed (RTK wrapper reported "ok") while git ls-remote shows the SHA never reached origin. The 5 review threads were then resolved on GitHub against the wrong commit, and the squash-merge captured only the original agent content.

Net: 5 known bugs are live in devkit master.

Severity

P1 — the underlying P0 from #1250 IS fixed (priceId map resolution works). These are hardening + observability gaps around it. The most operationally critical miss is the silent-free-fallback in resolvePlan — the exact failure mode #1250 was meant to prevent can silently recur if config.stripe.prices ever has a stale or missing entry.

Findings to re-apply

  1. JSDoc accuracy (buildPriceIdToPlanMap): comment described old behavior, missing @returns tag — replaced with accurate description of why the reverse-map exists and what it returns
  2. Silent free-fallback warn log (resolvePlan): return validatePlan(rawMeta) || 'free' had no warn when priceId was present but unmapped — added logger.warn so misconfigured config.stripe.prices is immediately visible in logs instead of silently downgrading paid orgs to free
  3. cancel_at typeof check: if (subscription.cancel_at) truthy check skips cancel_at = 0 (epoch) — changed to typeof === 'number'
  4. previousPlan validatePlan(): raw value from priceIdToPlan[previousPriceId] || metadata.planId was used without validation — stale/invalid metadata values would emit plan.changed and trigger forceRotateForPlanChange with junk plan names
  5. Test comment correction: comment above billing.failedBackfill.repository.js mock claimed "Suppress retryWithBackoff delays" which is incorrect — the mock makes the repo a no-op, it does not suppress setTimeout delays in retryWithBackoff

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    P1Critical — must be done first

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions