Skip to content

fix(billing): resolve plan via priceId map, not price.metadata.planId (P0 — silent prod downgrade) #3742

@PierreBrisorgueil

Description

@PierreBrisorgueil

Problem

modules/billing/services/billing.webhook.service.js customer.subscription.updated handler calls resolvePlan(subscription) which reads price.metadata.planId. price.metadata.planId is empty in real Stripe webhook payloads — Stripe only populates metadata on the Product object, not on the Price object surfaced through the Subscription update event.

Result: every paid org's plan gets reset to `free` on every Stripe `customer.subscription.updated` event. Every devkit downstream running Stripe billing (pierreb, comes, montaine, ism) is silently affected in production today.

Trawl fixed this in comes-io/trawl_node#1250 by building a priceId → plan map from config.stripe.prices and using that map instead. This issue ports the fix upstream so every downstream benefits via /update-stack.

Fix

Port from trawl:

  1. buildPriceIdToPlanMap() — builds Map<priceId, planName> from config.stripe.prices at boot
  2. Rewritten resolvePlan(subscription) — looks up plan via priceId map first; falls back to price.metadata.planId for legacy test fixtures; final fallback free with warn log
  3. cancelAt/cancelAtPeriodEnd sync on customer.subscription.updated (writes to fields added in feat(billing): subscription cancelAt + cancelAtPeriodEnd lifecycle fields #3741)
  4. handleSubscriptionCreated safety-net handler — race-safe Dashboard/Payment Link subscription creation
  5. retryWithBackoff + isNonTransientStripeError — new lib files billing.retry.js + billing.stripe-errors.js (port verbatim from trawl)

Strip trawl-specific analytics.capture('subscription_changed') calls.

Labels

P0, billing, stripe

References

  • Alignment plan: 2026-05-30-trawl-devkit-perfect-alignment.md Task C.2
  • Trawl fix: comes-io/trawl_node#1250

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    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